diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0a749ca..96d39ae 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -8,75 +8,61 @@ on: jobs: lint: - name: Lint + name: Static checks runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - uses: astral-sh/setup-uv@v7 with: enable-cache: true - - - name: Install Python - run: uv python install 3.12 - - - name: Install dev dependencies - run: uv sync --group dev - - - name: Run pre-commit - run: uv run pre-commit run --files $(find imread_benchmark tests -name "*.py" | tr '\n' ' ') + - run: uv python install 3.12 + - run: uv sync --frozen --group dev + - run: uv run pre-commit run --all-files test: - name: Test (Python ${{ matrix.python-version }}) + name: Tests (Python ${{ matrix.python-version }}) runs-on: ubuntu-latest + env: + CPATH: /opt/libjpeg-turbo/include + LD_LIBRARY_PATH: /opt/libjpeg-turbo/lib64 + LIBRARY_PATH: /opt/libjpeg-turbo/lib64 + PKG_CONFIG_PATH: /opt/libjpeg-turbo/lib64/pkgconfig strategy: + fail-fast: false matrix: python-version: ["3.11", "3.12", "3.13"] - fail-fast: false steps: - uses: actions/checkout@v4 - - uses: astral-sh/setup-uv@v7 with: enable-cache: true - - - name: Install Python ${{ matrix.python-version }} - run: uv python install ${{ matrix.python-version }} - - # System libs the [mainstream] wheels dlopen at import time: - # libjpeg-turbo8-dev — jpeg4py (built from sdist, needs headers). - # libturbojpeg0-dev — provides libturbojpeg.so.0 reliably across - # Ubuntu releases (noble renamed the runtime-only - # package to `libturbojpeg`; the `0-dev` name is - # the transitional metapackage that pulls the - # correct .so on both jammy and noble). - # libvips arrives via the `pyvips-binary` PyPI wheel, no apt install. - # Mirrors gcp/vm_startup.sh for cloud-CI parity. - - name: Install system C libraries + - run: uv python install ${{ matrix.python-version }} + - name: Install native JPEG libraries run: | sudo apt-get update -qq - sudo apt-get install -y -qq \ - libjpeg-turbo8-dev \ - libturbojpeg0-dev - - # `--extra mainstream` brings in opencv, skimage, imageio, kornia-rs, - # simplejpeg, PyTurboJPEG, imagecodecs, pyvips, Pillow, torch, - # torchvision, jpeg4py. With this, the cross-decoder agreement test - # actually compares 11 libs instead of just pillow. - - name: Install dev + mainstream deps - run: uv sync --group dev --extra mainstream --python ${{ matrix.python-version }} + sudo ./scripts/install-libjpeg-turbo.sh + - name: Install locked mainstream environment + run: uv sync --frozen --group dev --extra mainstream --python ${{ matrix.python-version }} + - name: Run complete suite + run: uv run --python ${{ matrix.python-version }} pytest -q - - name: Run tests - run: uv run --python ${{ matrix.python-version }} pytest tests/ -v + package: + name: Wheel and CLI smoke + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: astral-sh/setup-uv@v7 + - run: uv python install 3.12 + - run: uv sync --frozen --group dev + - run: uv build + - run: uv run imread-benchmark --help + - run: uv run imread-benchmark list-decoders shellcheck: - name: Shellcheck + name: GCP bootstrap shellcheck runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - - name: Install shellcheck - run: sudo apt-get install -y shellcheck - - - name: Check GCP scripts - run: shellcheck --severity=warning gcp/*.sh + - run: sudo apt-get update -qq && sudo apt-get install -y -qq shellcheck + - run: shellcheck --severity=warning gcp/*.sh + - run: bash -n gcp/run.sh gcp/run-many.sh gcp/vm_startup.sh diff --git a/.gitignore b/.gitignore index 3da1f1d..2594177 100644 --- a/.gitignore +++ b/.gitignore @@ -133,5 +133,6 @@ _internal/ /*.jpeg /*.pdf -# Legacy plot output directory (plots go under `_internal/plots/` via tools/create_plots.py) -/images/ +# Local schema-2 campaign state +/artifacts/ +/attempts/ diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index cdde1e4..a920408 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -3,58 +3,16 @@ ## Development setup ```bash -pip install uv -uv sync --group dev -``` - -## Local-only material (`_internal/`) - -The `_internal/` directory is **gitignored**. Keep drafts, papers, regenerated plots, PDFs, and scratch assets there — for example: - -- `_internal/papers/paper.md` — arXiv / manuscript drafts (do not add `paper.md` at repo root) -- `_internal/papers/generate_paper_assets.py` — thin wrapper; calls `python -m tools.paper_assets --all` to write `generated/*.md` + `figures/*` from `output/*.json` -- `_internal/plots/` — scratch figures from `tools/create_plots.py` (README plots live in `docs/assets/benchmarks/`) - -Benchmark result plots and paper claims must follow -[`docs/plotting_and_statistics.md`](docs/plotting_and_statistics.md): use -claim-first figures, keep exact matrices in tables, and avoid strict -faster/slower language unless raw-run uncertainty supports it. -- `_internal/assets/` — sample images, posters, one-off binaries -- `_internal/notebooks/` — local Jupyter notebooks (the repo ignores `*.ipynb`; do not commit them) - -Nothing under `_internal/` is tracked by git. - -## Preparing an anonymous review mirror - -The public repository intentionally keeps public identity links. If you need a -double-blind review artifact, create a separate branch or mirror and remove -identity surfaces there, not in regular public cleanup PRs. - -Checklist for that later anonymous branch: - -- remove the GitAds block from `README.md` -- remove or replace `.github/FUNDING.yml` -- replace the public citation block in `README.md` with an anonymous artifact note -- remove direct links to public GitHub profiles, public repository URLs, and author names -- replace personal or project-specific GCS examples with neutral placeholders if needed -- verify with `rg -i 'ternaus|iglovikov|vladimir|gitads|github sponsors|2501.13131'` -- keep `_internal/` ignored; do not publish manuscript drafts by accident - -## Running tests - -```bash -uv run pytest tests/ -v -``` - -## Running linters - -```bash +uv sync --frozen --group dev --extra mainstream +uv run pytest -q uv run pre-commit run --all-files ``` -## Adding a new JPEG decoder +Do not add an alternate runner, result JSON format, or plotting reader. The +schema-2 package, campaign, bundle, canonical loader, and publication path are +the only supported workflow. -### 1. Create the decoder +## Adding a decoder Create `imread_benchmark/decoders/_decoder.py`: @@ -62,111 +20,93 @@ Create `imread_benchmark/decoders/_decoder.py`: from __future__ import annotations import numpy as np + from imread_benchmark.decoders import BaseDecoder class FooDecoder(BaseDecoder): name = "foo" - package_name = "foo-package" # pip distribution name + package_name = "foo-package" + group = "mainstream" def decode(self, data: bytes) -> np.ndarray: import foo - # Must return (H, W, 3) uint8 RGB array - return foo.decode_jpeg(data) - # Optional: override if the library has a fast path-based API - # def decode_path(self, path: str) -> np.ndarray: - # import foo - # return foo.read_jpeg(path) + result = foo.decode(data) + return np.ascontiguousarray(result, dtype=np.uint8) ``` -The default `decode_path()` implementation calls `decode(Path(path).read_bytes())`, which is correct for all libraries. - -### 2. Register the decoder entry point - -In [`pyproject.toml`](pyproject.toml), add an entry under -`[project.entry-points."imread_benchmark.decoders"]`: - -```toml -entry-points."imread_benchmark.decoders".foo = "imread_benchmark.decoders.foo_decoder:FooDecoder" -``` +The adapter contract is a fully materialized, C-contiguous `(H, W, 3)` RGB +`uint8` array. The output must remain valid after the input buffer and every +library-local object are released. Lazy image handles, BGR output, CHW tensors, +borrowed buffers, and deferred conversion are invalid. -The runtime `REGISTRY` is built from these entry points. Do not hand-edit -`imread_benchmark/decoders/__init__.py` unless you are changing the registry -mechanism itself. +Pillow is the reference lifetime pattern: open in a context manager, call +`load()`, convert to RGB, materialize again, and copy to an owned NumPy array. -### 3. Add to a dependency group +Register the class under +`[project.entry-points."imread_benchmark.decoders"]` and add its distribution +to the appropriate optional dependency group: -In [`pyproject.toml`](pyproject.toml), add the pip distribution name to one of the `[project.optional-dependencies]` groups: +- `mainstream` for packages compatible with the PyTorch-oriented environment; +- `tensorflow` only for the separately resolved TensorFlow stack. -- `mainstream` — coexists with everything else (opencv, skimage, kornia-rs, torch, etc.). Use this unless you have a real conflict. -- `tensorflow` — only if your library hard-conflicts with torch. +Use the decoder class capability fields for genuine platform limitations: -(There used to be a third `pillow-simd` group; dropped 2026-04 — see [`docs/gcp_benchmarks.md`](docs/gcp_benchmarks.md#why-no-pillow-simd) for the reasoning. If you're adding a new Pillow fork that masks vanilla `PIL` in the same venv, add a fresh group rather than reusing this slot.) - -```toml -mainstream = [ - "opencv-python-headless", - ... - "foo-package", # platform-agnostic - "foo-package; sys_platform == 'linux'", # Linux only -] +```python +class FooDecoder(BaseDecoder): + skip_single = (("Darwin", "*"),) + skip_dataloader = (("Linux", "aarch64"),) + in_dataloader = True ``` -Add a platform marker if the wheel does not build everywhere. +Do not silently skip a failure discovered during a campaign. The plan preflight +must either reject an unsupported configuration or the support audit must record +the item-level failure before timing. -### 4. Encode platform skips on the class +## Required tests for a decoder -Nothing else needs editing — `REGISTRY` auto-discovers your decoder and the CLI runs it on every machine that supports it. If your library doesn't run everywhere, set the relevant ClassVars on your decoder: +1. bytes and path decode return normalized, owned/materialized output; +2. red-channel order test catches BGR output; +3. pixel agreement with the Pillow reference within the documented tolerance; +4. fixed/default thread-control behavior where applicable; +5. real DataLoader worker smoke if `in_dataloader` is true; +6. platform capability metadata and dependency-group discovery. -```python -class FooDecoder(BaseDecoder): - name = "foo" - package_name = "foo-package" - group = "mainstream" # which optional-dependencies group provides it - skip_single = [("Darwin", "*")] # don't run single-thread benchmark on macOS - skip_dataloader = [("Linux", "aarch64")] # don't run inside torch DataLoader on Arm Linux - in_dataloader = True # set False if it never makes sense in a DataLoader -``` +Run the full suite with the dependency group installed. A unit test that mocks +the decoder import is not a replacement for the real subprocess/worker smoke. -### 5. Document system deps +## Architecture invariants -If a system library is required (e.g. `brew install something`), add it to the **System Requirements** section in [`README.md`](README.md). +- One timed configuration runs in one fresh subprocess. +- Data transport, verification, validation, and warmup remain outside timing. +- DataLoader process start method is explicit and part of `config_id`. +- Support sets are immutable and pinned by ordered item IDs. +- Completed bundles are write-once and `COMMITTED.json` is written last. +- Environment provisioning uses `uv sync --frozen --no-editable`. +- GCP scripts are lifecycle/bootstrap only; experiment semantics belong in + typed Python modules. +- Publication code reads only the canonical bundle loader. -### 6. Verify +Any change to an identity field needs mutation tests showing that the relevant +ID changes. Any change to remote storage needs corruption, conflict, incomplete +commit, and resume tests. -```bash -# Tests auto-discover the decoder once its entry point is installed -uv run pytest tests/ -v +## Documentation and claims -# Quick smoke run via the orchestrator -imread-benchmark run --libs foo --mode single \ - --data-dir /path/to/imagenet/val \ - --num-images 100 --num-runs 2 -``` +Reader-facing benchmark claims must state protocol, workload, platform, output +contract, support policy, repetition count, and uncertainty. Decoder or loader +measurements cannot be described as training speed. -## Project structure +JPEG quality reconstructed from quantization tables is an estimate. Natural +FODB processing variants confound resize, encoder, quantization, subsampling, +and metadata changes; causal quality/resolution claims require the controlled +ablation built by `dataset controlled-package` and documented in +[docs/controlled_ablation.md](docs/controlled_ablation.md). -``` -imread_benchmark/ -├── decoders/ -│ ├── __init__.py # BaseDecoder, entry-point REGISTRY -│ ├── opencv_decoder.py -│ └── ... # one file per library -├── benchmark.py # timing loop with warmup -├── benchmark_single.py # CLI: single-library benchmark -├── benchmark_dataloader.py # CLI: DataLoader throughput benchmark -└── utils.py # system info, path helpers -tests/ -├── conftest.py # in-memory JPEG fixture -├── test_decoders.py # parametrized decode smoke tests -├── test_benchmark.py # timing loop tests -└── test_utils.py # utility function tests -tools/ -├── analyze_images.py # dataset statistics -├── create_plots.py # generate public README performance charts -├── paper_assets.py # generate local paper tables/figures under _internal/ -└── render_readme.py # refresh benchmark tables in README.md -pyproject.toml # 2 dependency groups under [project.optional-dependencies]: - # mainstream / tensorflow -``` +## Local-only material + +`_internal/` is ignored and may hold manuscript drafts or scratch outputs. It +must not become a second implementation, result format, or publication source. +Reusable plans, tests, schemas, and documentation belong in tracked package or +`docs/` files. diff --git a/README.md b/README.md index 338142b..6f277e5 100644 --- a/README.md +++ b/README.md @@ -1,299 +1,323 @@ # imread-benchmark -## Overview +`imread-benchmark` is a reproducible framework for measuring JPEG decoder and +PyTorch `DataLoader` supply throughput. It is built around immutable dataset +packages, deterministic experiment plans, fresh-process runs, and validated +schema-2 evidence bundles. -`imread-benchmark` is a reproducible benchmark framework for JPEG decoding in Python ML pipelines. It provides: +The repository no longer ships historical result JSON or a second execution +path. New paper tables and figures must be generated from committed schema-2 +bundles through the canonical publication layer. -- an installable `imread-benchmark` CLI for local datasets, -- isolated per-library worker environments so conflicting stacks can be benchmarked in one run, -- PyTorch `DataLoader` throughput measurements in addition to single-thread decoder speed, -- Google Cloud runners for repeatable cloud CPU comparisons, and -- JSON outputs plus generated plots/tables for README, docs, and publication-ready analysis. +Preprint: [Choosing a JPEG Decoder for PyTorch DataLoaders: Workload-Specific Throughput on Four CPUs](https://arxiv.org/abs/2605.08731). -The default benchmark uses the ImageNet validation set and reports RGB `uint8` decode throughput across common Python libraries and CPU families. +The paper fixes `num_workers=8` and compares each decoder with Pillow on the +same CPU and JPEG workload. It reports camera originals and social-media +processed JPEGs separately. The full worker sweep and absolute images-per-second +tables are in the appendix, so the main result stays a speed comparison rather +than a training-time claim. -Preprint: [Single-Thread JPEG Decoder Benchmarks Mis-Evaluate ML Data Loaders](https://arxiv.org/abs/2605.08731). +## What is measured -## Results +Two protocols are currently supported: -The plots and tables below are generated from `output//*.json`. To refresh after a new run: +- `decode-memory`: decode already-resident JPEG bytes into a fully materialized, + C-contiguous `(H, W, 3)` RGB `uint8` NumPy array; +- `loader-supply`: traverse a real PyTorch `DataLoader` over resident JPEG + bytes, including worker scheduling, batching, queues, and delivery to the + consumer process. -```bash -imread-benchmark plot --input output --output docs/assets/benchmarks -imread-benchmark render-readme -``` - -The figures below are claim-first summaries: full numeric matrices remain in the tables. - -![Protocol changes decoder recommendations](docs/assets/benchmarks/fig01_protocol_rank_change.png) - -![Worker-count scaling differs between AMD generations](docs/assets/benchmarks/fig02_amd_worker_delta.png) - -![TensorFlow JPEG decode shows a large ARM penalty](docs/assets/benchmarks/fig03_tensorflow_arm_penalty.png) - -![Near-optimality and platform range for operational decoder choice](docs/assets/benchmarks/fig04_decoder_recommendation_summary.png) - -### Single-thread decode throughput (img/s) - -Pure decode speed with one thread, bytes pre-loaded to memory. **Bold** = best per platform. - - - -| Library | AMD EPYC 9B14 | AMD EPYC 9B45 | Intel Xeon Platinum 8581C | Neoverse-N1 | Neoverse-V2 | -| :--- | ---: | ---: | ---: | ---: | ---: | -| `simplejpeg` | **690** | 857 | **735** | 456 | **662** | -| `turbojpeg` | 640 | 818 | 708 | 426 | 613 | -| `jpeg4py` | 636 | 760 | 699 | 423 | 611 | -| `kornia-rs` | 642 | 761 | 664 | 391 | 629 | -| `ajpegli` | 400 | 501 | 323 | 273 | 337 | -| `opencv` | 664 | 841 | 721 | 445 | 645 | -| `imagecodecs` | 677 | 775 | 723 | **457** | 661 | -| `pyvips` | 420 | 586 | 462 | 261 | 413 | -| `pillow` | 537 | 726 | 577 | 360 | 551 | -| `skimage` | 475 | 661 | 525 | 326 | 499 | -| `imageio` | 496 | 599 | 524 | 335 | 506 | -| `torchvision` | 621 | **864** | 712 | 440 | 643 | -| `tensorflow` | 596 | 836 | 689 | 268 | 391 | - - +Neither protocol measures storage download, archive verification, model +training, GPU transfer, or augmentation. Claims about epoch time require a +separate end-to-end experiment. -### Peak DataLoader throughput (img/s) +Every timed configuration runs in a fresh subprocess. Validation and warmup +are outside the timer. Pillow explicitly materializes the image, converts to +RGB, and copies it into an owned NumPy array before returning. -Best `images_per_second` across `num_workers ∈ {0, 2, 4, 8}` for each library × platform, using a PyTorch DataLoader with `batch_size=32`. Cell format: `img/s @ Nw`. **Bold** = best per platform. +## Evidence model - +A benchmark campaign pins: -| Library | AMD EPYC 9B14 | AMD EPYC 9B45 | Intel Xeon Platinum 8581C | Neoverse-N1 | Neoverse-V2 | -| :--- | ---: | ---: | ---: | ---: | ---: | -| `simplejpeg` | 1,521 @ 4w | 2,739 @ 8w | **1,754 @ 8w** | **1,557 @ 8w** | 2,421 @ 8w | -| `turbojpeg` | 1,535 @ 4w | 2,800 @ 8w | 1,710 @ 8w | 1,347 @ 4w | 2,389 @ 8w | -| `jpeg4py` | 1,443 @ 4w | 2,453 @ 8w | 1,651 @ 8w | 1,411 @ 8w | 2,312 @ 8w | -| `kornia-rs` | 1,327 @ 8w | 2,394 @ 8w | 1,422 @ 8w | 1,260 @ 8w | 1,951 @ 8w | -| `ajpegli` | 1,458 @ 8w | 2,882 @ 8w | 1,498 @ 8w | 1,448 @ 8w | 2,397 @ 8w | -| `opencv` | 1,457 @ 4w | 2,814 @ 8w | 1,707 @ 8w | 1,419 @ 8w | 2,414 @ 8w | -| `imagecodecs` | 1,543 @ 4w | 2,476 @ 8w | 1,677 @ 8w | 1,443 @ 8w | 2,242 @ 8w | -| `pillow` | 1,283 @ 4w | 2,465 @ 8w | 1,565 @ 8w | 1,387 @ 8w | 2,350 @ 8w | -| `skimage` | 1,238 @ 4w | 2,536 @ 8w | 1,615 @ 8w | 1,388 @ 8w | 2,315 @ 8w | -| `imageio` | 1,273 @ 4w | 2,324 @ 8w | 1,643 @ 8w | 1,466 @ 8w | **2,561 @ 8w** | -| `torchvision` | **1,596 @ 8w** | **2,920 @ 8w** | 1,612 @ 4w | 1,504 @ 8w | 2,557 @ 8w | +- exact JPEG bytes through `package_id`, `manifest_id`, and ordered item IDs; +- a pre-timing operational or common support set; +- decoder threads, DataLoader workers, batch size, prefetch, persistence, and + multiprocessing start method; +- lock-backed environment and stable platform descriptors; +- source revision, randomized repetition block, and run position. - +Each completed run is an immutable directory containing raw samples, phase +events, runtime worker probes, full provenance, derived statistics, payload +hashes, and a final `COMMITTED.json`. A result is invisible until the marker +and every checksum validate. - +## Article workloads -_**5 platforms** · 50,000 images · 5 runs each · latest run 2026-05-20_ +The article uses selected scenes from the Forchheim Image Database (FODB): - +- `fodb-native`: original camera JPEGs, providing the large-resolution regime; +- `fodb-mixed`: the same matched scenes and devices after Facebook, Instagram, + Telegram, Twitter, and WhatsApp processing, providing a realistic mixture of + resolutions, quantization tables, compression ratios, and metadata. -### What the results mean +These are workload comparisons, not a causal estimate of resolution or JPEG +quality. Encoder quality is generally unavailable; `quality_estimate` is only +an estimator derived from quantization tables. See +[Experiment design](docs/experiment_design.md) for the core matrix and the +controlled resolution × quality ablation needed for a causal mechanism claim. +The ablation builder and exact interpretation rules are documented in +[Controlled resolution and JPEG-quality ablation](docs/controlled_ablation.md). -Single-thread decoder speed is useful, but it is not enough to choose a decoder for a training pipeline. The peak `DataLoader` table is usually the better operational signal because it captures multiprocessing worker behavior, library fork-safety, and CPU-specific scaling. +## Setup -Current headline patterns: +Install [`uv`](https://docs.astral.sh/uv/) and sync the locked development +environment: -- `simplejpeg` is a strong single-thread baseline and wins peak `DataLoader` throughput on Intel Emerald Rapids and Neoverse N1. -- `torchvision` wins both AMD platforms at peak `DataLoader` throughput and is effectively tied for first on Neoverse V2. -- `imageio` is not a single-thread leader, but wins peak `DataLoader` throughput on Neoverse V2 in the current GCP runs. -- OpenCV is rarely the absolute winner, but is consistently close to the local winner and has successful `DataLoader` results on every platform. -- `ajpegli` has valid single-thread and `DataLoader` results, but skips one uncommon ImageNet JPEG in this matrix, so it is reported as a strict decoder rather than a zero-skip default. -- PyVips is reported for single-thread decode only; it is skipped in fork-based `DataLoader` benchmarks because of libvips threadpool deadlocks in this harness. - -## GitAds Sponsored - -[Sponsored by GitAds](https://gitads.dev/v1/ad-track?source=ternaus/imread_benchmark@github) - -## Important Note on Image Conversion - -All decoders output `(H, W, 3)` uint8 RGB numpy arrays for a fair comparison. Libraries that default to other formats (OpenCV → BGR, torchvision → CHW tensor, TensorFlow → EagerTensor) include a conversion step. Note that in real ML pipelines the conversion is often unnecessary. +```bash +uv sync --frozen --group dev --extra mainstream +uv run pytest -q +``` -## Benchmark Modes +These commands reproduce the committed dependency freeze. Before a new paper +campaign, update to the latest stable compatible set once with `uv lock +--upgrade`, run the full gate, and commit the new lock before any smoke. Do not +upgrade between machines. See [Experiment design](docs/experiment_design.md). -**Memory mode** (default): images are pre-loaded as bytes before the timed loop. This measures pure decode throughput with no disk I/O. +List decoder capability contracts: -**Disk mode**: each decode call reads the file from disk. Includes I/O latency. +```bash +uv run imread-benchmark list-decoders +``` -## Dataset +## Prepare FODB once -[ImageNet validation set](https://image-net.org) — 50,000 JPEG images, ~500×400px. +After downloading the FODB ZIP parts, build the selected native and mixed +workloads. The builder extracts only selected complete scenes, verifies ZIP +CRC values, records JPEG descriptors, hard-links the two local views, and +creates one deduplicated uncompressed tar package. ```bash -# Download -wget https://image-net.org/data/ILSVRC/2012/ILSVRC2012_img_val.tar -mkdir -p imagenet/val -tar -xf ILSVRC2012_img_val.tar -C imagenet/val +uv run imread-benchmark dataset fodb-package \ + --archive ~/data/fodb-part01.zip \ + --archive ~/data/fodb-part02.zip \ + --archive ~/data/fodb-part03.zip \ + --output-root ~/data/fodb-benchmark \ + --scene-count 12 \ + --seed 20260729 ``` -## System Requirements +Upload the returned descriptor to a private GCS prefix: ```bash -# macOS only: required by PyTurboJPEG (pure-python ctypes binding) -brew install jpeg-turbo +uv run imread-benchmark dataset publish \ + ~/data/fodb-benchmark/packages//package.json \ + --store gs://YOUR_BUCKET/imread \ + --prefix datasets ``` -`pyvips` ships its own bundled libvips via the `pyvips-binary` PyPI wheel, -so no `brew install vips` is needed. `simplejpeg` wheels bundle libjpeg-turbo. -On Linux you'll still need `apt install libjpeg-turbo8-dev libturbojpeg0` -(see `gcp/vm_startup.sh`), since `jpeg4py` is built from sdist. +The command returns the remote descriptor object key used by local and cloud +materializers. Dataset redistribution rights are not implied; keep the bucket +private and follow FODB's terms. -## Installation +## Build the controlled resolution-quality package -```bash -# Install uv if needed -pip install uv +For the separate causal ablation, start from a pinned lossless PNG source set +and generate every matched factor cell with one command: -# Install the lightweight orchestrator (control plane). -uv venv && source .venv/bin/activate -uv pip install -e . +```bash +uv run imread-benchmark dataset controlled-package \ + --source-dir /data/pinned-lossless-png \ + --output-root /data/controlled-jpeg \ + --source-name SOURCE_DATASET_NAME \ + --source-release SOURCE_DATASET_RELEASE \ + --source-license SOURCE_DATASET_LICENSE \ + --long-edge 512 --long-edge 1024 --long-edge 2048 \ + --quality 50 --quality 75 --quality 90 --quality 95 \ + --include-native \ + --subsampling 4:2:0 \ + --compressed-byte-limit 2147483648 ``` -The benchmark CLI creates decoder worker environments automatically under -`venvs//` when `imread-benchmark run` needs them. Today the groups are -`mainstream` and `tensorflow`; they are separate because TensorFlow and -PyTorch-oriented packages can have incompatible NumPy/protobuf constraints. -The first full run pays the dependency installation cost, and later runs reuse -the populated worker environments. Use `--skip-setup` only when those worker -environments already exist and should not be modified. +This produces 16 workloads with identical source membership and order. Generate +their plans from +[`examples/controlled-ablation.template.yaml`](examples/controlled-ablation.template.yaml) +with `plan instantiate`; do not interpret FODB's naturally confounded +size/quality mixture as this controlled effect. + +## Generate and validate plans before spending money + +An experiment plan is schema 2 YAML and must pin the IDs printed in +`package.json`. Example protocol profiles: + +```yaml +matrix: + decoders: + pillow: {threads: [default]} + opencv: {threads: [default, 1]} + simplejpeg: {threads: [default]} + torchvision: {threads: [default, 1]} + protocols: + decode-memory: {} + loader-supply: + worker_profiles: + - workers: [0] + batch_size: 1 + - workers: [2, 4, 8] + batch_size: 1 + prefetch_factor: 1 + persistent_workers: true + multiprocessing_start_method: spawn +execution: + per_run_subprocess: true + run_timeout_seconds: 3600 + checkpoint_each_run: true + maximum_memory_fraction: 0.6 +``` -Plotting is separate from benchmark execution. Install the plotting extra only -if you want to regenerate figures: +Generate one pinned, validated plan for each selected workload. Omit +`--workload` to generate plans for every workload in the package: ```bash -uv pip install -e '.[plot]' +uv run imread-benchmark plan instantiate \ + examples/fodb-experiment.template.yaml \ + --package-descriptor /data/packages//package.json \ + --output-dir plans/fodb \ + --workload fodb-native \ + --workload fodb-mixed ``` -## Running the Benchmark +The command prints each plan ID and its run count per platform. Inspect or +materialize the deterministic randomized matrix when needed: ```bash -# What would run on this machine? -imread-benchmark list-libs - -# Single + DataLoader for every supported decoder, default 50k images -imread-benchmark run --data-dir /path/to/imagenet/val +uv run imread-benchmark plan validate plans/fodb/fodb-native.yaml \ + --package-descriptor /data/packages//package.json -# Faster smoke run -imread-benchmark run --data-dir /path/to/imagenet/val \ - --num-images 2000 --num-runs 5 --dataloader-runs 2 \ - --workers 0,2 - -# Just one library, single-thread benchmark only -imread-benchmark run --data-dir /path/to/imagenet/val \ - --libs opencv --mode single - -# Generate README plots from output/ JSONs -imread-benchmark plot --input output --output docs/assets/benchmarks +uv run imread-benchmark plan expand plans/fodb/fodb-native.yaml \ + --package-descriptor /data/packages//package.json \ + --output expanded-plan.json ``` -The CLI sets up `venvs//` for each dependency group it needs. Subsequent runs reuse those venvs, so only the first invocation pays the install cost. +The complete five-block FODB matrix is available as +[`examples/fodb-experiment.template.yaml`](examples/fodb-experiment.template.yaml). +The generated workload plans are reused unchanged across CPU platforms; +captured platform identity remains part of every run key. +Before launching that matrix, instantiate +[`examples/fodb-smoke.template.yaml`](examples/fodb-smoke.template.yaml). It +contains the preregistered nine-run Pillow/OpenCV gate: both protocols, workers +0 and 2, one repetition block. -## Running on Google Cloud +## Run locally -Spin up a benchmark VM on GCP, run everything against ImageNet from a GCS bucket, and have it self-delete when done: +Capture platform provenance and provision the frozen worker environment: ```bash -./gcp/run.sh \ - --imagenet-bucket gs://my-bucket/imagenet/val \ - --results-bucket gs://my-bucket/imread-results \ - --no-wait +REVISION=$(git rev-parse HEAD) + +uv run imread-benchmark platform capture \ + --output artifacts/platform.json \ + --machine-type local \ + --location local + +uv run imread-benchmark environment provision \ + --group mainstream \ + --runner-revision "$REVISION" \ + --project-root . \ + --cache-root .cache/environments ``` -Built venvs are cached in GCS (keyed by `sha256(uv.lock)`), so reruns on the same machine type skip the ~25-minute install. Use `--force-rebuild` to re-resolve PyPI without editing `uv.lock`. Full details, machine-type matrix, cost, and cache semantics: [docs/gcp_benchmarks.md](docs/gcp_benchmarks.md). +Then pass the emitted environment descriptor and Python path to the campaign: -## Results Structure - -``` -output/ -└── linux_AMD-EPYC-9B45/ - ├── opencv_1t_results.json - ├── opencv_default_results.json - ├── opencv_dataloader_results.json - ├── run_summary.json - └── ... +```bash + -m imread_benchmark.cli campaign run experiment.yaml \ + --package-descriptor /data/packages//package.json \ + --environment-descriptor \ + --platform-descriptor artifacts/platform.json \ + --artifact-root artifacts \ + --attempts-root attempts \ + --runner-revision "$REVISION" \ + --worker-python ``` -## Libraries Benchmarked - -### Direct libjpeg-turbo (fastest) - -- **simplejpeg** — CFFI binding; zero-copy decode from bytes -- **turbojpeg** (PyTurboJPEG) — Python binding for libjpeg-turbo -- **jpeg4py** — direct libjpeg-turbo binding (**Linux only**) -- **kornia-rs** — Rust implementation using libjpeg-turbo -- **OpenCV** (opencv-python-headless) - -### Comprehensive codec libraries - -- **ajpegli** — Python wrapper for Google's JPEGli decoder; supports both - path-based reads and in-memory byte decoding. -- **imagecodecs** — uses libjpeg-turbo 3.x; prebuilt ARM64 wheels -- **pyvips** — libvips bindings (bundled in wheels). Single-thread only; - the libvips threadpool deadlocks under fork-based PyTorch DataLoader, - so dataloader benchmarks are skipped on every platform. - -### Standard libjpeg - -- **Pillow** -- **scikit-image** -- **imageio** +## Run on GCP -> **Note:** Pillow-SIMD was previously included but dropped 2026-04 — -> upstream is abandoned (last release 2023-05), no Linux wheels, and its -> historical SIMD speedup is now matched by `jpeg4py` / `simplejpeg` / -> `kornia-rs`. Full rationale in -> [`docs/gcp_benchmarks.md`](docs/gcp_benchmarks.md#why-no-pillow-simd). +The launcher uploads a content-addressed source snapshot and plan, creates an +ephemeral VM, materializes the dataset from one sequential GCS object, restores +or builds a content-addressed frozen environment, and checkpoints every completed bundle. `DONE` +or `FAILED` is written last; the VM then deletes itself unless failure retention +was explicitly requested. -### ML framework components - -- **torchvision** -- **tensorflow** - -## Performance Considerations - -- All benchmarks run single-threaded unless using the DataLoader benchmark -- Memory mode is the recommended baseline — it isolates decode speed from storage -- Results based on ImageNet JPEG images (~500×400px) +```bash +./gcp/run.sh \ + --plan experiment.yaml \ + --dataset-store gs://YOUR_BUCKET/imread \ + --dataset-descriptor datasets//package.json \ + --results-store gs://YOUR_BUCKET/imread-results \ + --environment-store gs://YOUR_BUCKET/imread-cache \ + --machine-type c4-standard-16 \ + --groups mainstream \ + --no-wait +``` -## Recommendations +Starting another VM with the same source, plan, package, environment, and +platform pulls valid committed bundles first and launches only missing runs. +See [GCP campaigns](docs/gcp_benchmarks.md). -### Choosing for ML training +## Validate and publish -- Use the DataLoader benchmark for final decoder and `num_workers` selection. -- Start with OpenCV when you need a robust default that runs everywhere. -- Try `torchvision` when your pipeline already wants tensors and you can benchmark the target CPU. -- Try lower-level native decoders such as `simplejpeg`, `turbojpeg`, `jpeg4py`, `kornia-rs`, or `ajpegli` when maximum decode speed matters and your dataset policy handles uncommon JPEG modes. +```bash +uv run imread-benchmark artifacts validate artifacts -### Choosing for pure decode speed +uv run imread-benchmark publish publication.yaml \ + --artifact-root artifacts \ + --output-dir generated -- Use the single-thread table to compare isolated decoder implementations. -- Re-run locally if your images differ substantially from ImageNet validation JPEGs. +uv run imread-benchmark publish publication.yaml \ + --artifact-root artifacts \ + --output-dir generated \ + --check +``` -### Choosing for feature-rich applications +Publication output includes raw sample values, repetition-level configuration +groups, and a provenance sidecar with all bundle IDs, filters, claim scope, +generator revision, and output hash. +Training claims are rejected for decoder-only or loader-only evidence. + +## Project structure + +```text +imread_benchmark/ + datasets/ package building, FODB selection, GCS materialization + environments/ descriptor, frozen provisioner, remote tar.zst cache + plans/ schema-2 plan loading and deterministic expansion + support/ pre-timing support audits and pinned intersections + execution/ campaign coordinator, one-run workers, attempts + artifacts/ atomic bundles and remote commit protocol + analysis/ canonical loader, statistics, claim gate, publication + decoders/ entry-point adapters and capability contracts +``` -- `opencv` remains the best choice when you need more than just JPEG decoding +## Contributing -## Development +Run the complete local gate before submitting changes: ```bash -# Run tests -uv run pytest tests/ -v - -# Run linters +uv run pytest -q uv run pre-commit run --all-files ``` -See [CONTRIBUTING.md](CONTRIBUTING.md) for how to add a new decoder. +See [CONTRIBUTING.md](CONTRIBUTING.md) for decoder adapter requirements. ## Citation -If you found this benchmark useful in your research or engineering work, please cite the preprint: - ```bibtex -@misc{iglovikov2026singlethreadjpegdecoderbenchmarks, - title={Single-Thread JPEG Decoder Benchmarks Mis-Evaluate ML Data Loaders}, - author={Vladimir Iglovikov}, - year={2026}, - eprint={2605.08731}, - archivePrefix={arXiv}, - primaryClass={cs.PF}, - url={https://arxiv.org/abs/2605.08731}, +@misc{iglovikov2026choosingjpegdecoder, + title={Choosing a JPEG Decoder for PyTorch DataLoaders: Workload-Specific Throughput on Four CPUs}, + author={Vladimir Iglovikov and Dmitry Kosarevsky}, + year={2026}, + eprint={2605.08731}, + archivePrefix={arXiv}, + primaryClass={cs.PF}, + url={https://arxiv.org/abs/2605.08731} } ``` diff --git a/docs/assets/benchmarks/fig01_protocol_rank_change.png b/docs/assets/benchmarks/fig01_protocol_rank_change.png deleted file mode 100644 index 549cff3..0000000 Binary files a/docs/assets/benchmarks/fig01_protocol_rank_change.png and /dev/null differ diff --git a/docs/assets/benchmarks/fig02_amd_worker_delta.png b/docs/assets/benchmarks/fig02_amd_worker_delta.png deleted file mode 100644 index 17fbcf0..0000000 Binary files a/docs/assets/benchmarks/fig02_amd_worker_delta.png and /dev/null differ diff --git a/docs/assets/benchmarks/fig03_tensorflow_arm_penalty.png b/docs/assets/benchmarks/fig03_tensorflow_arm_penalty.png deleted file mode 100644 index 382b1ae..0000000 Binary files a/docs/assets/benchmarks/fig03_tensorflow_arm_penalty.png and /dev/null differ diff --git a/docs/assets/benchmarks/fig04_decoder_recommendation_summary.png b/docs/assets/benchmarks/fig04_decoder_recommendation_summary.png deleted file mode 100644 index 352467c..0000000 Binary files a/docs/assets/benchmarks/fig04_decoder_recommendation_summary.png and /dev/null differ diff --git a/docs/controlled_ablation.md b/docs/controlled_ablation.md new file mode 100644 index 0000000..6c8df1e --- /dev/null +++ b/docs/controlled_ablation.md @@ -0,0 +1,125 @@ +# Controlled resolution and JPEG-quality ablation + +The article uses two complementary kinds of evidence: + +- FODB `fodb-native` and `fodb-mixed` are real workloads with naturally varying + resolution, quantization tables, metadata, processing service, and compressed + size. They show how measured decoder and DataLoader throughput changes across + a realistic mixture. +- The controlled package changes requested long edge and encoder quality while + holding source pixels, encoder build, chroma subsampling, metadata policy, + image order, and source membership fixed. It supports bounded mechanism + claims about those declared factors. + +Do not infer the effect of resolution or quality by regressing over FODB's +natural mixture. Those variables are confounded there. + +## Source contract + +Place the pinned source set in one directory tree as lossless, single-frame PNG +files. The builder rejects JPEG, TIFF, WebP, BMP, animated PNG, transparent PNG, +and any source smaller than the largest requested fixed long edge. This avoids +silently adding an earlier lossy encode, an alpha-compositing policy, upscaling, +or a format-dependent decoder to the experimental design. + +The command requires a source dataset name, release, and license, and stores +them in package provenance. It also records every relative path, source byte +hash, normalized RGB pixel hash, dimensions, and source ID. Absolute local +paths do not enter package identity. + +## Build all factor cells once + +The canonical grid is: + +- fixed long edge: 512, 1024, and 2048 pixels; +- a native-size reference level; +- Pillow encoder quality: 50, 75, 90, and 95; +- chroma subsampling: 4:2:0; +- progressive encoding and encoder optimization disabled; +- source metadata stripped; Pillow's fixed container headers retained; +- Pillow LANCZOS resize; +- one seeded source order shared by every cell. + +Build the package: + +```bash +uv run imread-benchmark dataset controlled-package \ + --source-dir /data/pinned-lossless-png \ + --output-root /data/controlled-jpeg \ + --source-name SOURCE_DATASET_NAME \ + --source-release SOURCE_DATASET_RELEASE \ + --source-license SOURCE_DATASET_LICENSE \ + --source-url SOURCE_DATASET_URL \ + --long-edge 512 \ + --long-edge 1024 \ + --long-edge 2048 \ + --quality 50 \ + --quality 75 \ + --quality 90 \ + --quality 95 \ + --include-native \ + --subsampling 4:2:0 \ + --seed 20260729 \ + --compressed-byte-limit 2147483648 +``` + +The result is one content-addressed package with 16 workloads such as +`controlled-le0512-q050`, `controlled-le2048-q095`, and +`controlled-native-q075`. Each workload contains exactly one generated JPEG per +source ID in the same order. The byte limit applies independently to every +complete resident workload and is checked before packaging. + +Pillow and its JPEG backend versions are part of package provenance. The JPEG +bytes and every source are content-addressed, so a different encoder build or +output changes the package ID even if the requested factors are unchanged. + +Publish this package to GCS with the same create-only command used for FODB: + +```bash +uv run imread-benchmark dataset publish \ + /data/controlled-jpeg/packages/PACKAGE_ID/package.json \ + --store gs://YOUR_BUCKET/imread \ + --prefix datasets +``` + +## Benchmark and analyze + +Generate all 16 workload plans from the package descriptor in one command: + +```bash +uv run imread-benchmark plan instantiate \ + examples/controlled-ablation.template.yaml \ + --package-descriptor /data/controlled-jpeg/packages/PACKAGE_ID/package.json \ + --output-dir plans/controlled +``` + +The command fills each workload's exact manifest ID and item count, then +validates and expands every result before returning it. Keep the platform, +decoder matrix, repetition count, thread profiles, source set, and measurement +settings identical across all 16 plans. + +The broad controlled grid measures `decode-memory`; this isolates decoder +capacity without multiplying the already large design by every worker count. +If loader-level follow-up is warranted, predeclare the decoder-selection rule +and run workers `{0, 2, 4, 8}` only for those cells and decoders. Do not describe +that targeted follow-up as a new exhaustive sweep. + +Use complete fresh-process repetition blocks as the independent samples. Pair +cells by platform, repetition, decoder/thread profile, and the recorded source +set. Report requested quality as an encoder setting, not as a universal JPEG +quality scale. The fixed 512/1024/2048 levels support a resolution contrast; +`native` is a matched real-size reference whose pixel dimensions still vary by +source. + +Acceptable claim: + +> Under the pinned Pillow encoder build, fixed 4:2:0 subsampling, and matched +> source images, changing the requested long edge from 512 to 2048 changed the +> measured decode-throughput ratio between decoders A and B on platform P. + +Unacceptable claim: + +> JPEG quality generally causes decoder A to beat decoder B. + +The latter exceeds one encoder, one subsampling policy, the tested factor +levels, and the named platforms. diff --git a/docs/experiment_design.md b/docs/experiment_design.md new file mode 100644 index 0000000..c046d50 --- /dev/null +++ b/docs/experiment_design.md @@ -0,0 +1,272 @@ +# Experiment design for the strengthened article + +## Claims first + +The paper should make three bounded claims: + +1. Isolated single-thread decode rank is not a reliable proxy for decoder-plus- + loader supply rank on named CPU platforms and workloads. +2. Decoder ranks and worker saturation points can change across a camera-native + high-resolution workload and a realistic social-media-processed workload. +3. Observed loader supply is only a component measurement and does not imply a + training-speed difference without measured consumer demand or an end-to-end + training protocol. + +The framework rejects a training claim when only `decode-memory` or +`loader-supply` bundles are selected. + +## Dataset regimes + +Use matched FODB scenes and devices so content changes less than it would across +unrelated corpora. + +| Regime | Purpose | Interpretation | +| --- | --- | --- | +| `fodb-native` | Large original camera JPEGs | High-resolution real-workload evidence | +| `fodb-mixed` | Original plus five platform-processed variants | Realistic mixture of sizes, quantization tables, compression ratios, and metadata | +| Controlled resolution × quality package | Mechanism ablation on pinned source images | Causal effect of the chosen re-encoding factors within this encoder | + +The first two are real workload comparisons. Resolution, processing service, +encoder implementation, quantization, metadata, and compression ratio change +together. Report JPEG `quality_estimate` only as an estimate derived from +quantization tables, never as the source encoder's true quality parameter. + +For a causal sentence such as “larger resolution changes decoder X relative to +decoder Y,” build the separate package from pinned lossless source images with +the canonical `dataset controlled-package` command. It declares and records: + +- long edge: 512, 1024, 2048, and native; +- encoder quality: 50, 75, 90, and 95; +- fixed chroma subsampling and metadata policy; +- identical source image IDs in every cell. + +Do not merge those rows with FODB and call the union a single resolution effect. +The full build and interpretation contract is in +[Controlled resolution and JPEG-quality ablation](controlled_ablation.md). + +## Dependency freeze + +Use the latest stable dependency set available at the preregistered freeze, +then keep it unchanged across every pilot and evidence run. The 2026-07-30 +freeze resolves the direct mainstream decoder dependencies as follows on the +Python 3.12 benchmark image: + +| Distribution | Frozen version | +| --- | ---: | +| ajpegli | 1.0.0 | +| imagecodecs | 2026.6.26 | +| imageio | 2.37.4 | +| jpeg4py (Linux only) | 0.1.4 | +| kornia-rs | 0.1.14 | +| opencv-python-headless | 5.0.0.93 | +| Pillow | 12.3.0 | +| PyTurboJPEG | 2.5.0 | +| pyvips | 3.1.1 | +| pyvips-binary | 8.18.4 | +| scikit-image | 0.26.0 | +| simplejpeg | 1.9.0 | +| torch | 2.13.0 | +| torchvision | 0.28.0 | + +PyTurboJPEG uses the verified upstream libjpeg-turbo 3.2.0 build on both +x86-64 and Arm. Transitive dependencies and Python-version-specific branches +are authoritative in `uv.lock`, rather than duplicated in this document. + +Create a new freeze before the first smoke, not between machines: + +```bash +uv lock --upgrade +uv sync --frozen --group dev --extra mainstream +uv run pytest -q +uv run pre-commit run --all-files +``` + +Commit `pyproject.toml`, `uv.lock`, adapter changes, and the native installer +together. The environment identity includes the lock hash, exact installed +distribution versions, and declared native backend versions. If a dependency +release is accepted after a smoke has produced evidence, discard that evidence +and restart every platform with the new environment identity. Otherwise defer +the release until the next benchmark revision. + +## Core matrix + +Start from [`examples/fodb-experiment.template.yaml`](../examples/fodb-experiment.template.yaml) +and generate the two pinned plans directly from the package descriptor: + +```bash +uv run imread-benchmark plan instantiate \ + examples/fodb-experiment.template.yaml \ + --package-descriptor /data/packages/PACKAGE_ID/package.json \ + --output-dir plans/fodb \ + --workload fodb-native \ + --workload fodb-mixed +``` + +The command fills the package, workload, manifest, and item-count identities, +then validates and expands each result before returning it. The two plans +deliberately remain distinct evidence populations and are reused unchanged +across CPU platforms. +TensorFlow belongs to its own incompatible dependency group and is not a +PyTorch DataLoader adapter. If its decoder-only result remains useful, run the +separate [`examples/fodb-tensorflow.template.yaml`](../examples/fodb-tensorflow.template.yaml) +as supplementary `decode-memory` evidence; do not insert it into loader curves. + +Use the following core GCP platform set at 16 vCPUs: + +| Machine type | CPU family | Role in the design | +| --- | --- | --- | +| `c4-standard-16` | Intel Xeon (runtime CPU captured) | x86 Intel reference | +| `c3d-standard-16` | AMD EPYC Genoa | x86 AMD Zen 4 | +| `c4d-standard-16` | AMD EPYC Turin | matched AMD Zen 5 generation contrast | +| `c4a-standard-16` | Google Axion, Arm Neoverse V2 | current Arm reference | + +These assignments follow the current +[Google Cloud machine-family documentation](https://docs.cloud.google.com/compute/docs/general-purpose-machines). +The captured CPU brand, family/model/stepping, architecture, VM type, zone, and +logical CPU count remain the evidence source; the marketing series name is not +used as a substitute for runtime provenance. Zone is execution provenance, not +a platform-identity factor: capacity may move a run between zones. A platform +group requires matching captured machine type, CPU identity, architecture, and +logical CPU count. Publication output keeps every raw platform ID and zone, so +the grouping is inspectable and a zone-specific anomaly remains visible. + +For each named CPU platform and each real workload: + +- all supported mainstream decoders under `normalized-rgb`; +- `decode-memory` with decoder default threads and, only for adapters with + verified thread control, an explicitly requested setting; +- `loader-supply` with workers `{0, 2, 4, 8}`; +- batch size 1 and prefetch factor 1 for high-resolution FODB, persistent + workers, and an explicit start method; +- five randomized repetition blocks for the broad matrix; +- one complete timed traversal per fresh-process block; the five blocks, rather + than repeated passes inside one process, are the independent repetitions; +- every timed pass long enough to exceed the preregistered minimum duration, + using logical repeats rather than copying JPEG bytes. + +Thread profiles belong to each decoder in the plan, not to the protocol as a +whole. Plan expansion rejects a requested thread count when the adapter cannot +set and report it, before any cloud VM is launched. + +The dense sweep `{0,1,2,4,6,8,12,16}` is not part of the broad matrix. Workers +1 and 6 add cost without answering a primary claim, and high worker counts can +make decoded in-flight memory dominate on large native images. + +Use an adaptive second stage only when a pilot shows one of these conditions: + +- throughput is still increasing materially at 8 workers; +- the peak is at the tested boundary; +- the curve is non-monotonic enough that its location is unclear; +- runtime worker probes or memory telemetry suggest an oversubscription + mechanism worth isolating. + +Extend one boundary at a time. First add `12` only for the affected decoder × +platform × workload cells, with the same repetition policy. This is a targeted +mechanism experiment, not a post-hoc replacement for the declared core matrix. + +Freeze each follow-up stage before observing its new worker count. A cell enters +the `workers=12` stage when its controlled-thread loader configuration has its +highest observed mean at `workers=8` over the completed `{0,2,4,8}` matrix. The +stage adds five fresh-process repetitions at `workers=12` and leaves every +other cell unchanged. Before launching a possible `workers=16` stage, freeze a +material and repeat-consistent stopping rule: the mean at 12 must exceed the +mean at 8 by at least 5%, and all five paired 12/8 block ratios must be at +least one. This avoids spending a second extension on a numerically maximal but +practically flat boundary. It is an operational follow-up rule, not a +statistical-significance claim. + +The completed FODB matrix selects 87 of 96 controlled decoder × platform × +workload cells for the first extension, for 435 additional bundles. The +checked-in follow-up templates encode the frozen `workers=12` selection: + +| Workload and platform | Eligible decoders | Template | +| --- | ---: | --- | +| FODB-mixed, all four platforms | 12 | `fodb-worker-followup-all.template.yaml` | +| FODB-native, AMD Zen 5 and Axion | 12 | `fodb-worker-followup-all.template.yaml` | +| FODB-native, Intel 8581C | 8 | `fodb-worker-followup-native-intel.template.yaml` | +| FODB-native, AMD Zen 4 | 7 | `fodb-worker-followup-native-zen4.template.yaml` | + +The completed 12-worker stage selected 58 configurations for 16 workers, or +290 fresh-process bundles: all 48 FODB-mixed cells, eight Intel-native cells, +and the two Axion-native cells for `ajpegli` and Pillow. Native Zen 4 and Zen 5 +cells did not meet the rule. The corresponding checked-in templates are +`fodb-worker-followup-w16-mixed.template.yaml`, +`fodb-worker-followup-w16-native-intel.template.yaml`, and +`fodb-worker-followup-w16-native-axion.template.yaml`. + +Neither stage turns the naturally heterogeneous FODB workloads into a causal +test of resolution or JPEG quality. + +Every plan declares `execution.maximum_memory_fraction`. Campaign preflight +conservatively counts resident compressed-byte replicas (including `spawn` or +`forkserver` copies), prefetched decoded RGB batches, the consumer batch, and +one decoder temporary per worker. A configuration that exceeds the declared +fraction of measured platform RAM is rejected before support audit or timing. +The package's compressed-byte cap bounds each complete resident workload; it +does not split one logical run into separately timed shards. + +## Support and failures + +Run support audits before timing. Preserve two views: + +- operational support: all items accepted by one decoder/context; +- common support: the ordered intersection for a declared comparison group. + +Headline decoder comparisons use common support. Report operational coverage +and every excluded item separately. A timed failure invalidates the run; it is +not converted into a faster partial traversal. + +## Repetitions and uncertainty + +The unit of repetition is a complete randomized run block, not an individual +image. Keep every timed-pass sample. Report the mean, median, sample standard +deviation (`ddof=1`), and `n`; for `n=1`, sample standard deviation is null. + +Use paired within-block ratios when comparing configurations that share +platform, workload, support items, protocol, output contract, and repetition. +Treat differences inside the declared practical margin as practically tied. +Do not turn overlapping noisy means into a strict ranking sentence. + +When a capacity retry moves a run to another zone, retain the zone and complete +platform descriptor with that run. The run remains in the same platform group +only when the captured non-zone identity matches. Do not make a zone-level +performance claim from that grouping. + +## Pilot gates before the full campaign + +1. Package audit: hashes, manifest counts, resolution/compressed-size strata, + provenance distribution, and decoded-memory bound. +2. One x86 smoke from + [`examples/fodb-smoke.template.yaml`](../examples/fodb-smoke.template.yaml): + Pillow and OpenCV, both protocols, workers 0 and 2, exactly nine run specs. +3. One ARM smoke with the same run specs. +4. Kill after K of N runs; verify a fresh VM launches exactly N−K run workers. +5. Verify every bundle, environment descriptor, platform descriptor, worker + handshake, and publication sidecar. +6. Freeze the final matrix before launching the full platform sweep. + +Instantiate the smoke plan against `fodb-native` before launching either +machine: + +```bash +uv run imread-benchmark plan instantiate \ + examples/fodb-smoke.template.yaml \ + --package-descriptor /data/packages/PACKAGE_ID/package.json \ + --output-dir plans/smoke \ + --workload fodb-native +``` + +## Figures and tables + +Build figures only after selecting the claim they support: + +- protocol rank-change slopegraph or paired-ratio plot; +- worker curves for representative inversion/saturation cases, with raw + repetition points and uncertainty; +- workload transfer plot comparing native and mixed common-support ratios; +- exact appendix matrices for every platform/workload/configuration; +- coverage table for operational/common support and failures. + +Every caption names protocol, workload, platform, output contract, support +policy, repetition count, and uncertainty statistic. Raw samples and bundle IDs +remain machine-readable in publication output. diff --git a/docs/fodb_workloads.md b/docs/fodb_workloads.md new file mode 100644 index 0000000..4c72f22 --- /dev/null +++ b/docs/fodb_workloads.md @@ -0,0 +1,77 @@ +# FODB workload preparation + +The FODB builder creates two matched real-world workloads without loading the +full source corpus into RAM: + +- `fodb-native`: original camera JPEGs from selected complete scenes; +- `fodb-mixed`: those originals plus Facebook, Instagram, Telegram, Twitter, + and WhatsApp variants for the same scenes and devices. + +FODB is used instead of the 350 GB RAISE RAW collection because this benchmark +measures JPEG decoding. RAW files would first need an arbitrary JPEG encoding +step and would no longer be a real encoded workload. + +## Selection + +The builder scans ZIP central directories, ignores inspection files, and keeps +only scenes containing the complete declared device × provenance matrix. It +ranks scene IDs by a seed-pinned SHA-256 rule and chooses 12 by default. ZIP +member order therefore cannot change the selection. + +For every selected JPEG it verifies the ZIP CRC, computes SHA-256, and records: + +- width, height, component count, progressive/baseline status, and subsampling; +- quantization tables and their digest; +- an explicitly labelled IJG-style quality estimate; +- scan count and EXIF/ICC/other metadata sizes; +- compressed bytes and bits per decoded pixel; +- device, scene, and processing provenance. + +The quality estimate is not the encoder's original `quality=` setting. Social +services may resize, strip metadata, change subsampling, or use different +quantization logic at once. + +## Build + +```bash +uv run imread-benchmark dataset fodb-package \ + --archive ~/data/fodb-part01.zip \ + --archive ~/data/fodb-part02.zip \ + --archive ~/data/fodb-part03.zip \ + --output-root ~/data/fodb-benchmark \ + --scene-count 12 \ + --seed 20260729 +``` + +Selected members are extracted once. Native and mixed views use hard links, so +they add directory entries rather than duplicate JPEG payload. The final schema-2 +package deduplicates identical content again under `blobs/.jpg` and +stores one uncompressed tar plus manifests and a random-access offset index. + +## Memory bound + +The builder rejects a selected native or mixed workload whose total compressed +bytes exceed `--compressed-byte-limit`. It checks the bound before extracting +JPEGs. A run then reads the complete pinned workload from the package tar into +memory once; logical repeats lengthen timed passes without copying those bytes. +This keeps every timed pass on one identical population instead of combining +separately timed shards. + +With `spawn` or `forkserver`, resident JPEG bytes are serialized to each worker, +so the campaign preflight counts `workers + 1` compressed copies. It also counts +prefetched decoded RGB arrays from width/height metadata. The high-resolution +paper plan uses batch size 1 and prefetch factor 1. The broad sweep stops at 8 +workers; 12 and 16 are allowed only when the pilot still scales and the complete +conservative estimate fits the plan's RAM fraction. + +## Upload + +```bash +uv run imread-benchmark dataset publish \ + ~/data/fodb-benchmark/packages//package.json \ + --store gs://YOUR_BUCKET/imread \ + --prefix datasets +``` + +Keep the GCS prefix private. The command uses create-only object publication; +an existing object with different bytes is a conflict, not an overwrite. diff --git a/docs/gcp_benchmarks.md b/docs/gcp_benchmarks.md index 5765912..fd3400e 100644 --- a/docs/gcp_benchmarks.md +++ b/docs/gcp_benchmarks.md @@ -1,430 +1,119 @@ -# Running Benchmarks on Google Cloud +# GCP campaigns -The scripts in `gcp/` are the cloud replication framework for `imread-benchmark`. -They spin up a GCP VM, run the same local `imread-benchmark` CLI against ImageNet -from a GCS bucket, upload JSON results and logs back to GCS, and **delete the VM -when done**. No machine time is wasted. You can start a run, close your laptop, -and fetch results in the morning. +The GCP path is a thin VM lifecycle wrapper around the same schema-2 Python CLI +used locally. Shell does not contain decoder matrices, dataset-specific branches, +or result semantics. -Use this path when you want reproducible CPU comparisons across GCP machine -families, not just a one-off local benchmark. +## Before launch ---- +1. Build and publish the dataset package. +2. Generate pinned workload plans from the package descriptor with `plan + instantiate`. +3. Validate and expand the plan locally. +4. Confirm the target service account can read the private dataset prefix, + create result/cache objects, and delete its own VM. -## Prerequisites - -### 1. Install Google Cloud SDK +For example: ```bash -brew install --cask google-cloud-cli -gcloud auth login -gcloud config set project YOUR_GCP_PROJECT_ID +uv run imread-benchmark plan instantiate \ + examples/fodb-experiment.template.yaml \ + --package-descriptor /data/packages/PACKAGE_ID/package.json \ + --output-dir plans/fodb \ + --workload fodb-native \ + --workload fodb-mixed ``` -### 2. Upload ImageNet val to GCS (one-time) - -Do this once. Every subsequent benchmark run pulls from GCS — no local upload per run. +For the first real-cloud gate, use +[`examples/fodb-smoke.template.yaml`](../examples/fodb-smoke.template.yaml) +against `fodb-native`. It expands to nine runs: Pillow and OpenCV under both +protocols, with loader workers 0 and 2. -**Recommended: upload as a single tarball.** Per-file downloads on the VM bottleneck on -GCS small-file API overhead (~50 ms × 50,000 files = 40+ min); a single 5.5 GB tarball -pulls in ~30 sec at same-region GCS bandwidth and is then untarred locally. - -```bash -cd ~/data/imagenet # parent of the val/ directory -tar -cf val.tar val/ -gcloud storage cp val.tar gs://my-bucket/imagenet/val.tar -``` - -`vm_startup.sh` auto-detects `.tar` and uses the fast path. For -smoke runs (`--smoke`, `--num-images 200`) it untars everything and trims down to N -locally — still faster than per-file fetching even though most of the tarball is -discarded, because hyperdisk-balanced extraction is essentially free. - -**Fallback: per-file directory upload** (kept working for existing buckets that -predate the tarball convention): +## Launch ```bash ./gcp/run.sh \ - --upload-imagenet ~/data/imagenet/val \ - --imagenet-bucket gs://my-bucket/imagenet/val -``` - -If both `/` (per-file dir) and `.tar` (single tarball) exist, the -tarball wins. To force the slow path for testing, rename or delete the tarball. - ---- - -## Running Benchmarks - -### Start a run and go to bed - -```bash -./gcp/run.sh \ - --imagenet-bucket gs://YOUR_BUCKET/imagenet/val \ - --results-bucket gs://YOUR_BUCKET/imread-results \ + --plan experiment.yaml \ + --dataset-store gs://YOUR_BUCKET/imread \ + --dataset-descriptor datasets//package.json \ + --results-store gs://YOUR_BUCKET/imread-results \ + --environment-store gs://YOUR_BUCKET/imread-cache \ + --machine-type c4-standard-16 \ + --zone us-west4-a \ + --groups mainstream \ --no-wait ``` -This prints a one-liner to fetch results, then exits immediately. -**Close your laptop. The VM runs independently.** - -What happens while you sleep: - -1. A `c3-standard-8` VM boots on GCP (Intel Sapphire Rapids, 8 vCPU) -2. Downloads ImageNet from GCS to local disk (~30s within GCP, tarball fast path) -3. Runs all supported library benchmarks in memory mode (1-thread + default-thread) — ~15-25 min -4. Runs requested DataLoader benchmarks at 50k images, workers ∈ {0, 2, 4, 8} — ~25-45 min -5. Uploads results to `gs://YOUR_BUCKET/imread-results//output/` -6. **Deletes itself** — no orphaned VM, no ongoing billing - -### In the morning - -```bash -# Check if done (exit 0 = object exists) -gcloud storage objects describe gs://YOUR_BUCKET/imread-results//DONE - -# Fetch results -gcloud storage cp --recursive gs://YOUR_BUCKET/imread-results//output/ ./output/ -``` - -The exact commands are printed by `--no-wait` when you start the run. +Use `--groups mainstream,tensorflow` only when the plan contains configurations +for both dependency groups. Each group gets its own frozen environment and +campaign pass; stable run keys prevent overlap. -### Watch the log mid-run (optional) +## VM sequence -```bash -gcloud storage cat gs://YOUR_BUCKET/imread-results//startup.log -``` +1. Install minimal system libraries and `uv`. +2. Download and checksum the exact source snapshot and plan. +3. Install a small non-editable control environment with `uv sync --frozen`. +4. Download the dataset package components, verify every hash and tar member, + and atomically publish a read-only local cache entry. +5. Capture platform identity and runtime metadata. +6. Restore a content-addressed normalized `tar.zst` frozen worker environment from GCS, or + build it with `uv sync --frozen --no-editable` and publish the cache. +7. Run pre-timing support audits in fresh processes. +8. Pull already committed run bundles, execute only missing run specs in fresh + processes, and publish each successful bundle immediately. +9. Upload logs/attempt status, write `DONE.json` last, and delete the VM. -This streams the full VM stdout, including per-library progress, without SSH. +On failure the script uploads `FAILED.json` last and deletes the VM by default. +`--keep-on-failure` retains it for SSH diagnosis and therefore requires manual +cleanup. ---- +## Identity and resume -## Stay and wait (blocks until done) +The launcher computes `runner_revision` from sorted source paths and their file +hashes. It does not use a timestamped tar checksum. Repacking unchanged source +therefore preserves run keys. -If you want the script to block, download results, and exit all in one command: +A timestamp appears only in the job/attempt name. Canonical bundles live at +stable remote keys: -```bash -./gcp/run.sh \ - --imagenet-bucket gs://YOUR_BUCKET/imagenet/val \ - --results-bucket gs://YOUR_BUCKET/imread-results +```text +artifacts/bundles// +artifacts/runs//COMMITTED.json ``` -Progress is printed every 30 seconds. Results land in `./output/` when done. -Press Ctrl+C at any point — the VM is deleted immediately via a cleanup trap. +The commit marker is uploaded with an object-generation create-only precondition. +On restart, only markers with available bundles and valid hashes count as done. +Incomplete prefixes are ignored. -### Run one decoder - -Use `--libs` when you only need new data for one decoder. For example, to add -`ajpegli` results without rerunning every library: - -```bash -./gcp/run.sh \ - --machine-type c4-standard-16 \ - --smoke \ - --libs ajpegli \ - --imagenet-bucket gs://YOUR_BUCKET/imagenet/val \ - --results-bucket gs://YOUR_BUCKET/imread-results -``` - -For the full paper platform matrix: +## Multiple machines ```bash ./gcp/run-many.sh \ - --machine-types "c4-standard-16 c3d-standard-16 c4d-standard-16 c4a-standard-16 t2a-standard-16" \ - --libs ajpegli \ - --imagenet-bucket gs://YOUR_BUCKET/imagenet/val \ - --results-bucket gs://YOUR_BUCKET/imread-results -``` - ---- - -## Options - - -| Flag | Default | Description | -| ------------------- | --------------- | -------------------------------------------------------------------- | -| `--imagenet-bucket` | (required) | GCS path to ImageNet val directory | -| `--results-bucket` | (required) | GCS bucket for results and logs | -| `--zone` | `us-central1-a` | GCP zone | -| `--machine-type` | `c3-standard-8` | VM type (Intel Sapphire Rapids, 8 vCPU, 32 GB) | -| `--libs` | `all` | Comma-separated decoder names, or `all` | -| `--num-images` | `50000` | Number of images per benchmark run (full ImageNet val) | -| `--num-runs` | `5` | Single-thread timed runs per library (see [Sample size](#sample-size))| -| `--dl-runs` | `3` | DataLoader timed runs per worker config | -| `--workers` | `0 2 4 8` | DataLoader worker counts (drop `1` — torch impl detail vs `0`) | -| `--no-wait` | off | Fire-and-forget; return immediately after VM creation | -| `--smoke` | off | Tiny validation run on a new machine type (~10 min, ~$0.10) | -| `--no-cache` | off | Skip the venv cache entirely (forces a cold install on the VM) | -| `--force-rebuild` | off | Re-resolve PyPI on the VM and reupload the cache (see below) | -| `--upload-imagenet` | — | Upload local ImageNet path to `--imagenet-bucket`, then exit | - - -All flags can also be set as environment variables (`IMAGENET_BUCKET`, `RESULTS_BUCKET`, -`ZONE`, `MACHINE_TYPE`, `NUM_IMAGES`). - ---- - -## Venv cache - -Cold installing decoder libraries on the VM takes ~25 minutes. To avoid paying that on -every run, the first run on a given `(os, arch, deps)` combo tars `venvs/` and uploads it -to `gs:///imread-cache/`. Subsequent runs pull and extract it in seconds. - -Default cache bucket is sibling to your results bucket: if you pass -`--results-bucket gs://my-bucket/imread-results`, the cache lands in -`gs://my-bucket/imread-cache/`. Override with `CACHE_BUCKET=gs://...` or disable per-run -with `--no-cache`. - -### Cache key - -``` -venvs---.tar.zst -``` - -Only `uv.lock` is hashed — it already pins every package, version, hash, extra, and index -URL, so no other input adds information. Cosmetic edits to `pyproject.toml` -(formatter, README link, ruff config) do not bust the cache. - -### When new library versions ship to PyPI - -Cached venvs are **frozen** at whatever `uv.lock` resolved to. New releases on PyPI never -sneak in just because the upstream package shipped — that's the whole point: bit-identical -benchmark inputs across reruns. To pull in newer versions, pick one: - -```bash -# Option A: explicit, recorded in git, picked up on the next run automatically. -uv lock --upgrade -git add uv.lock && git commit -m "chore: refresh deps" -./gcp/run.sh ... - -# Option B: ad-hoc PyPI re-resolve without editing the lockfile (e.g. monthly -# "is anything faster now" sweep). Bypasses cache lookup, runs `uv pip install` -# fresh on the VM, and OVERWRITES the cached tarball under the same key. -./gcp/run.sh --force-rebuild ... -``` - -Old cache entries stick around forever in GCS (keyed by old hash), so historical -benchmarks remain reproducible: `git checkout && ./gcp/run.sh ...` rebuilds the -exact env from then. - ---- - -## Cost - - -| Component | Estimate | -| ----------------------------------------- | ------------------------------ | -| VM compute (`c3-standard-8`) | ~$0.33/hr × 1h ≈ **$0.35** | -| GCS storage (results, ~5 MB) | negligible | -| GCS egress (ImageNet download within GCP) | free (same region) | -| **Total per run** | **~$0.40** (full benchmark) | - - -The VM self-deletes after benchmarks complete, so there is no idle billing. -The 60 GB boot disk is deleted along with the VM. - -ImageNet val in GCS costs ~$0.13/month to store. Upload it once, reuse forever. - ---- - -## How It Works - -``` -LOCAL MACHINE GCS BUCKET GCP VM -───────────────────────────────────────────────────────────────────── -run.sh packs repo (git archive) - → uploads repo.tar.gz [repo.tar.gz] - → uploads vm_startup.sh [vm_startup.sh] - -gcloud instances create ←startup-script -(returns immediately) - - VM boots - vm_startup.sh runs: - [imagenet/val] → downloads ImageNet - runs benchmarks - [output/] ← uploads results - [DONE] ← writes sentinel - REST API delete call - VM is gone - -run.sh polls [DONE] every 30s - → finds DONE - → downloads [output/] to ./output/ - → exits -``` - ---- - -## Troubleshooting - -**VM vanished without a DONE sentinel** - -The VM crashed. Check the log: - -```bash -gcloud storage cat gs://my-bucket/imread-results//startup.log -``` - -**"no active gcloud account" error** - -```bash -gcloud auth login -gcloud config set project YOUR_PROJECT_ID -``` - -**A library failed but others succeeded** - -`imread-benchmark run` skips failed libraries and continues. Results for successful -libraries are still uploaded. Check the log for `WARN:` lines. - -**Want to cancel a running VM** - -```bash -gcloud compute instances list # find the instance name -gcloud compute instances delete imread-benchmark-TIMESTAMP --zone=us-central1-a --quiet -``` - -**Add Linux-only libraries (`jpeg4py`)** - -Already in the decoder registry. `imread-benchmark run` honors the per-decoder -platform-skip metadata, so it automatically participates when you launch on a -matching VM (Linux for jpeg4py). - -## Regenerating public and paper assets - -After fetching `output/` from GCS, regenerate public README assets with: - -```bash -imread-benchmark plot --input output --output docs/assets/benchmarks -imread-benchmark render-readme -``` - -Publication-style tables and figures use the same JSON outputs but write into the -ignored `_internal/papers/` workspace: - -```bash -uv run --extra plot python -m tools.paper_assets --all -``` - -For the public arXiv preprint workspace specifically: - -```bash -uv run --extra plot python -m tools.paper_assets --all --paper-dir _internal/papers/arxiv_preprint -``` - -Do not commit `_internal/` outputs. They are local manuscript artifacts; the -tracked source of truth is the benchmark JSON plus the generator code. - ---- - -## Sample size - -The defaults (`N=50000`, `--num-runs 5`, `--dl-runs 3`, `--workers "0 2 4 8"`) -are picked to give publication-grade precision in the smallest wallclock the -statistics actually justify. Earlier revisions used 50000 × 20 single-thread -runs and 50000 × 5 dataloader runs across `0 1 2 4 8` workers — that was ~4× -slower than necessary for zero gain in any number we cite. - -For paper figures and wording, see -[`docs/plotting_and_statistics.md`](plotting_and_statistics.md). In short: -tables report `mean ± sample std`, but comparative claims should use the raw -`raw_throughput_ips` samples plus practical thresholds. Treat DataLoader gaps -below about 5% as top-tier ties unless raw-run uncertainty clearly separates -them. - -### Why N = 50000 - -Full ImageNet val. Two reasons: - -1. **No sampling defense in a report.** "We use the standard ImageNet val - set (N = 50 000)" needs no justification. "We sampled 10 000 images" needs - a paragraph on selection method, seed, representativeness, and why N is - below the natural unit. -2. **Doesn't drive wallclock anyway.** Per-run time is `N / throughput`. The - slow decoders (skimage, imageio at ~200 img/s) take ~4 min/run at N=50k; - cutting N to 10k saves ~3 min/run, but that's the entire decoder's runtime - compressed, not the bottleneck. The bottleneck is `N × num_runs`, and - `num_runs` is the cheaper lever. - -### Why num_runs = 5 (down from 20) - -With N = 50 000 and per-image σ/μ ≈ 0.4 for JPEG decode time, the standard -error on the *mean throughput* across `N × num_runs` decodes is: - - SE_mean ≈ (σ_per_image / sqrt(N × num_runs)) / μ ≈ 0.4 / sqrt(N × num_runs) - -For 50000 × 5 that's ~0.08% relative SE. The interesting effect sizes we -compare are 2× to 100× (decoder throughput differences). Even 1% SE is more -precision than the benchmark decision can use; 0.08% is theatrical. - -`num_runs` matters for *per-run percentiles* (p50/p90/p99 across runs), not -for mean precision. With `num_runs = 5`: - -- `p50` = median of 5 ≈ stable -- `p90`, `p99` = essentially the max-of-5, no useful resolution - -We dropped these columns from publication tables. The headline is `mean ± std img/s`, -and `num_runs = 5` gives a stable std estimate (5 samples is the practical -minimum for a non-degenerate sample variance). - -### Why dl-runs = 3 - -DataLoader runs are dominated by worker spawn variance and the first-batch -warmup (subprocess fork + library re-import). After the first warmup pass -the per-run noise is small; 3 timed reps is enough for `mean ± std`. Going -to 5+ doesn't surface anything that 3 misses. - -### Why workers = "0 2 4 8" (dropped `1`) - -`num_workers=0` and `num_workers=1` both run the decoder in a single process; -the `0` path skips the worker IPC entirely (in-thread iteration), the `1` -path forks one worker. The wall-clock difference is a torch implementation -detail, not a property of any decoder. Nobody plots it. Keeping `0` (no-fork -baseline), `2` (fork sanity, catches pyvips-on-Arm-style deadlocks), `4`, -and `8` (scaling on 8-vCPU machines). - -### If you want the old protocol back - -```bash -./gcp/run.sh --num-runs 20 --dl-runs 5 --workers "0 1 2 4 8" ... -``` - -It's an env-var override, no code change. Useful if you specifically want -percentile data for a separate latency-tail study. - ---- - -## Why no Pillow-SIMD? - -Pillow-SIMD was included in the original (2019-2024) revisions of this -benchmark and was dropped 2026-04. Reasons: - -1. **Abandoned upstream.** Last release `9.5.0.post2` (May 2023). The repo - (`uploadcare/pillow-simd`) has had no commits, no issue triage, and no - wheel uploads since. It is permanently behind mainstream Pillow on every - security and performance fix. -2. **No Linux wheels.** The project ships only an sdist on PyPI, so every - install builds from source and pulls in `build-essential`, `zlib1g-dev`, - `libjpeg-turbo8-dev` at C-compile time. On a cold-cache GCP VM that's - another ~90 s of apt + ~60 s of compile per run, in exchange for a - library nobody is shipping. -3. **No industry footprint.** PyPI download stats put `pillow-simd` at - <0.1% of `pillow`'s volume. None of the modern training stacks - (PyTorch DataLoader default, torchvision, FFCV, DALI, webdataset) pin - it; they use either vanilla Pillow or a turbojpeg-backed reader. -4. **Superseded.** The historical SIMD speed-up over vanilla Pillow on - JPEG decode (~2-3×) is now matched or beaten by `jpeg4py`, - `simplejpeg`, `PyTurboJPEG`, and `kornia-rs` — all of which are - actively maintained, ship Linux wheels, and are already in this - benchmark. -5. **Silent invalidation risk.** Any package that installs vanilla - `Pillow` into the same venv (e.g. torchvision's transitive pin) - downgrades pillow-simd in place — the import still says `PIL`, but - the SIMD intrinsics are gone. The reported number would be a lie - without obvious warning. - -If you specifically need a pillow-simd vs Pillow comparison, the decoder, -dependency group, and CI wiring are preserved in git history — search for -the commit that touches `imread_benchmark/decoders/pillow_simd_decoder.py` -and revert it locally. + --machine-types "c4-standard-16 c3d-standard-16 c4d-standard-16 c4a-standard-16" \ + --plan experiment.yaml \ + --dataset-store gs://YOUR_BUCKET/imread \ + --dataset-descriptor datasets//package.json \ + --results-store gs://YOUR_BUCKET/imread-results \ + --environment-store gs://YOUR_BUCKET/imread-cache +``` + +The wrapper tries fallback zones only for capacity errors and runs machine types +sequentially. Zone is recorded in each bundle as execution provenance, while +the canonical platform identity uses the captured machine and CPU properties +that determine comparability. This lets a capacity retry reuse the same +platform group without hiding where each run executed. + +## Fault drill + +Before the evidence campaign: + +1. launch a smoke plan with at least three run specs; +2. terminate the VM after K committed runs; +3. relaunch the identical source and plan on the same machine type; use a + different zone if capacity requires it; +4. verify the new campaign reports K skipped and N−K completed runs; +5. validate all local or downloaded bundles with `artifacts validate`. + +The test suite exercises the same remote commit, corruption, timeout, process +tree termination, and N−K resume semantics against real subprocesses and a fake +object store. A real x86 and ARM GCP smoke remains a release/evidence gate. diff --git a/docs/plotting_and_statistics.md b/docs/plotting_and_statistics.md index f45c82c..143e5a4 100644 --- a/docs/plotting_and_statistics.md +++ b/docs/plotting_and_statistics.md @@ -1,123 +1,60 @@ -# Plotting and Statistical Claim Guide - -This project uses figures to support paper claims, not to duplicate numeric tables. -Exact benchmark matrices belong in generated tables; plots should make one claim -legible in a first pass through the paper or README. - -## Claim-first figure set - -1. **Protocol changes recommendations** - - Preferred plot: diverging rank-change barplot, not a dense slopegraph. - - Metric: `single_thread_rank - peak_dataloader_rank`. - - Positive values mean the decoder moves up under `DataLoader`; negative values mean it moves down. - - Use representative Intel, AMD, and ARM panels unless a claim explicitly needs two generations from one vendor. - - Caption must say that full throughput matrices are in the tables. - -2. **AMD generations change worker-count effectiveness** - - Preferred plot: paired AMD panels showing `% change from w=4 to w=8`. - - Use a zero line; encode positive and negative directions with distinct colors. - - Do not write "AMD scales poorly"; write that Zen 4 and Zen 5 differ under this worker policy. - -3. **TensorFlow ARM penalty** - - Preferred plot: normalized bar/dot chart. - - Metric: TensorFlow single-thread throughput as `% of the platform single-thread winner`. - - This avoids mixing CPU speed with decoder behavior and makes the ARM-specific drop visible. - -4. **Overall performance and recommendations** - - Preferred plot: range/lollipop plot of each decoder's peak `DataLoader` throughput as `% of platform winner`. - - Show min/mean/max across platforms and individual platform points. - - Use tables for exact values and robustness; the recommendation figure should emphasize stable near-optimality. - -## General claim-to-plot mapping - -Use this section when the paper claims change. Pick the plot from the evidence -shape, not from habit. - -| Claim shape | Preferred plot | Use when | Avoid | -| --- | --- | --- | --- | -| A vs B changes the conclusion | Difference barplot, diverging barplot, paired dot plot | Comparing protocols, workers, settings, before/after, or ablations | Side-by-side grouped bars when readers must mentally subtract | -| Ranking changes | Rank-change barplot or small bump chart with few highlighted items | Showing that evaluation protocol changes rank/tier | Dense slopegraphs with many decoders | -| One item moves up/down relative to a baseline | Delta barplot or waterfall-style difference chart | Showing improvement/regression by decoder, worker count, platform, or version | Raw absolute bars unless absolute scale is the point | -| Time/worker scaling curve | Small-multiple line chart with shared axes | Showing monotonicity, saturation, collapse, or nonlinearity over workers/threads | One crowded line chart with all platforms and all decoders | -| Hardware/platform interaction | Small multiples or interaction plot | Showing behavior changes by CPU generation, vendor, ISA, SMT, or architecture | Collapsing hardware into one average | -| Cross-platform stability | Range/lollipop, box/strip plot, min/mean/max dot range | Showing robust defaults or platform sensitivity | Ranking by mean alone | -| Near-optimal recommendation | Normalized range plot: `% of local winner` | Choosing a practical default across heterogeneous platforms | Absolute throughput across CPUs without normalization | -| Single-platform leaderboard | Horizontal bar chart with error bars | Showing one platform's descriptive ordering | Claiming deployment recommendations from it | -| Full benchmark matrix | Table; optional supplementary heatmap | Preserving exact values across decoders × platforms | Main-text heatmap for directional claims | -| Distribution/noise | Dot/strip plot of raw runs, CI interval plot, violin only if enough samples | Showing run variance, outliers, or uncertainty | Hiding raw samples behind only means | -| Robustness/failures | Table with counts and example errors | Skips, unsupported modes, deadlocks, eligibility | Treating rare failures as a throughput scatter unless failure rate is the claim | -| Cost/runtime tradeoff | Pareto scatter or frontier plot | Throughput vs setup cost, dependency cost, memory, failure rate | Single scalar score unless weighting is justified | -| Composition/share | Stacked bar only when parts sum to a meaningful whole | Breaking wall time into decode/I/O/overhead components | Stacked bars for unrelated metrics | -| Correlation | Scatter with regression only if causal story is not implied | Exploring relation between image size, throughput, or variance | Causal wording from correlation | -| Categorical recommendation | Decision table or rubric | Translating benchmark evidence into user choices | Overloaded figure with too many dimensions | - -Default hierarchy: - -1. If the claim is comparative, plot the **difference**. -2. If the claim is about recommendation, plot **normalized distance from the local best/top tier**. -3. If the claim is about exact values, use a **table**. -4. If the claim is about uncertainty, show **raw samples or intervals**. -5. If the claim is about many platforms or decoders, use **small multiples** rather than one crowded panel. - -## Visual rules - -- Use **tables** for exact values and **figures** for claim support. -- Use aligned bars or dot/range plots for comparisons. Avoid grouped bars when the target task is a difference. -- Avoid slopegraphs/bump charts with many decoders unless only one or two highlighted lines matter. -- Avoid heatmaps as main evidence for directional claims; heatmaps are acceptable as supplementary overviews. -- Sort bars by the quantity that supports the claim, usually delta or normalized performance, not alphabetically. -- Use shared axes across small multiples when the viewer is expected to compare panels. -- Normalize within platform when comparing decoders across different CPUs. -- Keep absolute throughput when the claim is capacity planning for one known machine. -- Put units in axis labels and captions; avoid encoding units only in title text. -- Use log scale only when multiplicative differences are the claim and explain it in the caption. -- Include the denominator in normalized plots, e.g. `% of platform winner`. -- Color should encode meaning: - - improvement or moving up: green/blue - - regression or moving down: red/orange - - background or non-highlighted items: gray -- Label only the largest movers or the claim-critical decoders. Too many labels turn plots into tables. -- Use neutral titles that state the measured effect, not a causal story the experiment cannot isolate. -- Captions must state the protocol, platform subset, normalization, and where the full table lives. - -These rules follow common visualization evidence from comparison-chart research: -difference charts make A-vs-B changes explicit, crowded rank charts have high -cognitive load, and benchmark papers typically separate complete numeric tables -from compact claim figures. - -## Statistical methodology - -The raw JSON contains the samples needed for uncertainty-aware comparisons: - -- single-thread rows: `benchmark_results.raw_throughput_ips`, usually 5 runs -- `DataLoader` rows: `worker_results[*].raw_throughput_ips`, usually 3 runs per worker count - -Use this policy for paper claims: - -1. Report descriptive tables as `mean ± sample std`. -2. For "faster/slower" claims, compare raw run samples, not only means. -3. Apply practical-significance thresholds before using strict faster/slower language: - - single-thread practical threshold: 1% relative throughput - - `DataLoader` practical threshold: 5% relative throughput, because worker-process variance is larger -4. Treat decoders as the same top tier when the measured gap is below the practical threshold or the raw-run samples make the ordering uncertain. -5. Prefer "top tier", "near-optimal", "not clearly separated", and "rank order changes" over strict winner language. -6. For peak `DataLoader` worker count, choose the highest mean for generated tables, but text should mention ties/top tiers when adjacent worker counts are uncertain or practically indistinguishable. -7. Do not mention confidence intervals, bootstrap intervals, or error bars in paper claims unless the implementation actually computes those intervals for the reported comparison. - -The current checked-in artifact has 5 single-thread runs and 3 `DataLoader` -runs per worker count. That is enough to prevent obvious overclaiming, but it is -not enough to justify fine-grained claims about 1-2% differences in `DataLoader` -throughput. - -## Bad claims to avoid - -- "Decoder X is universally fastest." -- "Single-thread winner is the best training-loader choice." -- "AMD scales poorly." -- "ARM is slow for JPEG decode." -- "TensorFlow is always slow." -- "OpenCV is fastest." -- "PyVips is broken." Use "unsupported in this fork-based PyTorch DataLoader harness." -- "Strict native JPEG decoders are wrong." Use "they require an explicit fallback policy for uncommon JPEG modes." -- "Rank 1 beats rank 2" when their uncertainty or practical gap makes them a top tier. -- Cross-platform absolute throughput comparisons without explaining CPU speed and microarchitecture. +# Plotting, statistics, and claims + +## Canonical inputs + +Only validated schema-2 run bundles are evidence. `summary.json` is a derived +cache; the canonical loader preserves every raw timed-pass sample, event, +failure row, runtime probe, and provenance document. + +## Statistics + +For elapsed seconds, images per second, and microseconds per image, report: + +- number of timed-pass samples; +- arithmetic mean; +- median; +- sample standard deviation with `ddof=1`; +- null standard deviation when `n=1`. + +The repetition block, not an individual JPEG, is the independent scheduling +unit. When comparing configurations, prefer paired within-block ratios after +matching platform, workload, protocol, output contract, support items, and +repetition. + +Do not pool thousands of per-image decodes and present them as thousands of +independent benchmark repetitions. Do not infer a strict winner from small, +noisy differences inside the declared practical margin. + +## Claim scope + +- `decode-memory` supports statements about resident-byte decoder component + throughput under the normalized RGB contract. +- `loader-supply` supports statements about supply to a Python consumer under + the pinned DataLoader configuration. +- neither supports a claim about model training throughput. + +The publication claim gate enforces these boundaries. + +## Figure selection + +Use the smallest figure that answers a declared question: + +- paired-ratio or slopegraph for protocol rank change; +- worker curves with raw repetition points for saturation; +- matched workload-transfer plot for native versus mixed FODB; +- exact table for the complete matrix and support coverage. + +Every axis includes units. Every caption names platform, workload, protocol, +output contract, support policy, repetitions, and uncertainty statistic. +Avoid truncated axes for bar charts, excessive decimal precision, unlabeled +normalization, and decorative charts without a paper claim. + +## Quality and resolution + +FODB's natural variants are intentionally heterogeneous. Stratifying them by +megapixels, bits per pixel, quantization-table digest, or quality estimate is a +descriptive workload analysis. It does not identify a causal “quality effect.” + +A causal figure needs the controlled resolution × quality package described in +[Experiment design](experiment_design.md), with the encoder, source images, +subsampling, and metadata policy pinned. diff --git a/docs/reproducing_results.md b/docs/reproducing_results.md new file mode 100644 index 0000000..ab851bd --- /dev/null +++ b/docs/reproducing_results.md @@ -0,0 +1,71 @@ +# Reproducing publication artifacts + +The repository intentionally contains no historical result matrix. A publication +artifact must start from a frozen set of schema-2 bundles produced by the named +plan, dataset package, environments, source revision, and platforms. + +Reproduction uses the committed `uv.lock`; do not upgrade dependencies while +reproducing a campaign. The one-time latest-version freeze happens before the +first pilot and is documented in [Experiment design](experiment_design.md). +Each environment descriptor records the lock hash, exact installed +distributions, and declared native backend versions. + +## Validate evidence + +```bash +uv sync --frozen --group dev +uv run imread-benchmark artifacts validate artifacts +``` + +Validation checks the exact file set, every payload hash and size, bundle and run +identity, package/support/config IDs, ordered items, sample indices and counts, +runtime thread/worker probes, required phase events, environment/platform +descriptors, and the complete summary recomputed from raw samples. + +Before a campaign starts, plan validation pins its timeout and maximum RAM +fraction. Campaign preflight rejects configurations whose conservative resident +compressed-data plus decoded-prefetch estimate exceeds that fraction of the +captured platform memory. + +## Publication specification + +Example: + +```yaml +schema_version: "2.0" +claim_scope: loader-supply +filters: + config.protocol_id: loader-supply + dataset.workload_id: fodb-mixed +statistic: images_per_second +practical_margin_percent: 5 +generator_revision: <40-or-64-character-source-revision> +``` + +Generate: + +```bash +uv run imread-benchmark publish publication.yaml \ + --artifact-root artifacts \ + --output-dir generated +``` + +Verify byte-for-byte freshness in CI: + +```bash +uv run imread-benchmark publish publication.yaml \ + --artifact-root artifacts \ + --output-dir generated \ + --check +``` + +`results.json` contains exact selected bundle rows and raw timed-pass values, +plus configuration groups aggregated across complete repetition blocks. +Group-level `n` is the number of independent run blocks; `raw_run_means` keeps +the corresponding per-run means and sample standard deviation uses `ddof=1`. +`provenance.json` records every bundle ID/run key, filters, claim scope, +generator revision, practical margin, result hash, and publication ID. + +Do not copy summary values manually into another plotting script. Figures and +tables should consume this checked publication output or the canonical bundle +loader with an equivalent provenance sidecar. diff --git a/examples/controlled-ablation.template.yaml b/examples/controlled-ablation.template.yaml new file mode 100644 index 0000000..318b9c1 --- /dev/null +++ b/examples/controlled-ablation.template.yaml @@ -0,0 +1,40 @@ +schema_version: "2.0" +experiment_name: controlled-WORKLOAD_ID +seed: 20260729 +repetitions: 5 +dataset: + descriptor: /data/datasets/PACKAGE_ID/package.json + package_id: PACKAGE_ID + workload_id: WORKLOAD_ID + manifest_id: MANIFEST_ID + selection: + method: all + expected_items: ITEM_COUNT + logical_repeat_factor: 1 +matrix: + decoders: + ajpegli: {threads: [default]} + imagecodecs: {threads: [default]} + imageio: {threads: [default]} + jpeg4py: {threads: [default]} + kornia: {threads: [default]} + opencv: {threads: [default, 1]} + pillow: {threads: [default]} + pyvips: {threads: [default, 1]} + simplejpeg: {threads: [default]} + skimage: {threads: [default]} + torchvision: {threads: [default, 1]} + turbojpeg: {threads: [default]} + protocols: + decode-memory: {} +measurement: + warmup_passes: 1 + timed_passes_per_run: 1 + minimum_timed_seconds: 1.0 + output_contract: normalized-rgb + support_policy: common +execution: + per_run_subprocess: true + run_timeout_seconds: 3600 + checkpoint_each_run: true + maximum_memory_fraction: 0.6 diff --git a/examples/fodb-experiment.template.yaml b/examples/fodb-experiment.template.yaml new file mode 100644 index 0000000..d9315d0 --- /dev/null +++ b/examples/fodb-experiment.template.yaml @@ -0,0 +1,49 @@ +schema_version: "2.0" +experiment_name: fodb-WORKLOAD_ID +seed: 20260729 +repetitions: 5 +dataset: + descriptor: /data/datasets/PACKAGE_ID/package.json + package_id: PACKAGE_ID + workload_id: WORKLOAD_ID + manifest_id: MANIFEST_ID + selection: + method: all + expected_items: ITEM_COUNT + logical_repeat_factor: 1 +matrix: + decoders: + ajpegli: {threads: [default]} + imagecodecs: {threads: [default]} + imageio: {threads: [default]} + jpeg4py: {threads: [default]} + kornia: {threads: [default]} + opencv: {threads: [default, 1]} + pillow: {threads: [default]} + pyvips: {threads: [default, 1]} + simplejpeg: {threads: [default]} + skimage: {threads: [default]} + torchvision: {threads: [default, 1]} + turbojpeg: {threads: [default]} + protocols: + decode-memory: {} + loader-supply: + worker_profiles: + - workers: [0] + batch_size: 1 + - workers: [2, 4, 8] + batch_size: 1 + prefetch_factor: 1 + persistent_workers: true + multiprocessing_start_method: spawn +measurement: + warmup_passes: 1 + timed_passes_per_run: 1 + minimum_timed_seconds: 1.0 + output_contract: normalized-rgb + support_policy: common +execution: + per_run_subprocess: true + run_timeout_seconds: 3600 + checkpoint_each_run: true + maximum_memory_fraction: 0.6 diff --git a/examples/fodb-smoke.template.yaml b/examples/fodb-smoke.template.yaml new file mode 100644 index 0000000..9cc3065 --- /dev/null +++ b/examples/fodb-smoke.template.yaml @@ -0,0 +1,39 @@ +schema_version: "2.0" +experiment_name: fodb-smoke-WORKLOAD_ID +seed: 20260729 +repetitions: 1 +dataset: + descriptor: /data/datasets/PACKAGE_ID/package.json + package_id: PACKAGE_ID + workload_id: WORKLOAD_ID + manifest_id: MANIFEST_ID + selection: + method: all + expected_items: ITEM_COUNT + logical_repeat_factor: 1 +matrix: + decoders: + opencv: {threads: [default, 1]} + pillow: {threads: [default]} + protocols: + decode-memory: {} + loader-supply: + worker_profiles: + - workers: [0] + batch_size: 1 + - workers: [2] + batch_size: 1 + prefetch_factor: 1 + persistent_workers: true + multiprocessing_start_method: spawn +measurement: + warmup_passes: 1 + timed_passes_per_run: 1 + minimum_timed_seconds: 1.0 + output_contract: normalized-rgb + support_policy: common +execution: + per_run_subprocess: true + run_timeout_seconds: 3600 + checkpoint_each_run: true + maximum_memory_fraction: 0.6 diff --git a/examples/fodb-tensorflow.template.yaml b/examples/fodb-tensorflow.template.yaml new file mode 100644 index 0000000..dfa0913 --- /dev/null +++ b/examples/fodb-tensorflow.template.yaml @@ -0,0 +1,29 @@ +schema_version: "2.0" +experiment_name: fodb-WORKLOAD_ID-tensorflow +seed: 20260729 +repetitions: 5 +dataset: + descriptor: /data/datasets/PACKAGE_ID/package.json + package_id: PACKAGE_ID + workload_id: WORKLOAD_ID + manifest_id: MANIFEST_ID + selection: + method: all + expected_items: ITEM_COUNT + logical_repeat_factor: 1 +matrix: + decoders: + tensorflow: {threads: [default, 1]} + protocols: + decode-memory: {} +measurement: + warmup_passes: 1 + timed_passes_per_run: 1 + minimum_timed_seconds: 1.0 + output_contract: normalized-rgb + support_policy: operational +execution: + per_run_subprocess: true + run_timeout_seconds: 3600 + checkpoint_each_run: true + maximum_memory_fraction: 0.6 diff --git a/examples/fodb-worker-followup-all.template.yaml b/examples/fodb-worker-followup-all.template.yaml new file mode 100644 index 0000000..438aa20 --- /dev/null +++ b/examples/fodb-worker-followup-all.template.yaml @@ -0,0 +1,46 @@ +schema_version: "2.0" +experiment_name: fodb-worker-followup-all-WORKLOAD_ID +seed: 20260729 +repetitions: 5 +dataset: + descriptor: /data/datasets/PACKAGE_ID/package.json + package_id: PACKAGE_ID + workload_id: WORKLOAD_ID + manifest_id: MANIFEST_ID + selection: + method: all + expected_items: ITEM_COUNT + logical_repeat_factor: 1 +matrix: + decoders: + ajpegli: {threads: [default]} + imagecodecs: {threads: [default]} + imageio: {threads: [default]} + jpeg4py: {threads: [default]} + kornia: {threads: [default]} + opencv: {threads: [1]} + pillow: {threads: [default]} + pyvips: {threads: [1]} + simplejpeg: {threads: [default]} + skimage: {threads: [default]} + torchvision: {threads: [1]} + turbojpeg: {threads: [default]} + protocols: + loader-supply: + worker_profiles: + - workers: [12] + batch_size: 1 + prefetch_factor: 1 + persistent_workers: true + multiprocessing_start_method: spawn +measurement: + warmup_passes: 1 + timed_passes_per_run: 1 + minimum_timed_seconds: 1.0 + output_contract: normalized-rgb + support_policy: common +execution: + per_run_subprocess: true + run_timeout_seconds: 3600 + checkpoint_each_run: true + maximum_memory_fraction: 0.6 diff --git a/examples/fodb-worker-followup-native-intel.template.yaml b/examples/fodb-worker-followup-native-intel.template.yaml new file mode 100644 index 0000000..9704586 --- /dev/null +++ b/examples/fodb-worker-followup-native-intel.template.yaml @@ -0,0 +1,42 @@ +schema_version: "2.0" +experiment_name: fodb-worker-followup-native-intel-WORKLOAD_ID +seed: 20260729 +repetitions: 5 +dataset: + descriptor: /data/datasets/PACKAGE_ID/package.json + package_id: PACKAGE_ID + workload_id: WORKLOAD_ID + manifest_id: MANIFEST_ID + selection: + method: all + expected_items: ITEM_COUNT + logical_repeat_factor: 1 +matrix: + decoders: + ajpegli: {threads: [default]} + imageio: {threads: [default]} + kornia: {threads: [default]} + opencv: {threads: [1]} + pillow: {threads: [default]} + pyvips: {threads: [1]} + skimage: {threads: [default]} + torchvision: {threads: [1]} + protocols: + loader-supply: + worker_profiles: + - workers: [12] + batch_size: 1 + prefetch_factor: 1 + persistent_workers: true + multiprocessing_start_method: spawn +measurement: + warmup_passes: 1 + timed_passes_per_run: 1 + minimum_timed_seconds: 1.0 + output_contract: normalized-rgb + support_policy: common +execution: + per_run_subprocess: true + run_timeout_seconds: 3600 + checkpoint_each_run: true + maximum_memory_fraction: 0.6 diff --git a/examples/fodb-worker-followup-native-zen4.template.yaml b/examples/fodb-worker-followup-native-zen4.template.yaml new file mode 100644 index 0000000..9d5bf61 --- /dev/null +++ b/examples/fodb-worker-followup-native-zen4.template.yaml @@ -0,0 +1,41 @@ +schema_version: "2.0" +experiment_name: fodb-worker-followup-native-zen4-WORKLOAD_ID +seed: 20260729 +repetitions: 5 +dataset: + descriptor: /data/datasets/PACKAGE_ID/package.json + package_id: PACKAGE_ID + workload_id: WORKLOAD_ID + manifest_id: MANIFEST_ID + selection: + method: all + expected_items: ITEM_COUNT + logical_repeat_factor: 1 +matrix: + decoders: + ajpegli: {threads: [default]} + imageio: {threads: [default]} + opencv: {threads: [1]} + pillow: {threads: [default]} + pyvips: {threads: [1]} + skimage: {threads: [default]} + torchvision: {threads: [1]} + protocols: + loader-supply: + worker_profiles: + - workers: [12] + batch_size: 1 + prefetch_factor: 1 + persistent_workers: true + multiprocessing_start_method: spawn +measurement: + warmup_passes: 1 + timed_passes_per_run: 1 + minimum_timed_seconds: 1.0 + output_contract: normalized-rgb + support_policy: common +execution: + per_run_subprocess: true + run_timeout_seconds: 3600 + checkpoint_each_run: true + maximum_memory_fraction: 0.6 diff --git a/examples/fodb-worker-followup-w16-mixed.template.yaml b/examples/fodb-worker-followup-w16-mixed.template.yaml new file mode 100644 index 0000000..76c7e37 --- /dev/null +++ b/examples/fodb-worker-followup-w16-mixed.template.yaml @@ -0,0 +1,46 @@ +schema_version: "2.0" +experiment_name: fodb-worker-followup-w16-mixed-WORKLOAD_ID +seed: 20260729 +repetitions: 5 +dataset: + descriptor: /data/datasets/PACKAGE_ID/package.json + package_id: PACKAGE_ID + workload_id: WORKLOAD_ID + manifest_id: MANIFEST_ID + selection: + method: all + expected_items: ITEM_COUNT + logical_repeat_factor: 1 +matrix: + decoders: + ajpegli: {threads: [default]} + imagecodecs: {threads: [default]} + imageio: {threads: [default]} + jpeg4py: {threads: [default]} + kornia: {threads: [default]} + opencv: {threads: [1]} + pillow: {threads: [default]} + pyvips: {threads: [1]} + simplejpeg: {threads: [default]} + skimage: {threads: [default]} + torchvision: {threads: [1]} + turbojpeg: {threads: [default]} + protocols: + loader-supply: + worker_profiles: + - workers: [16] + batch_size: 1 + prefetch_factor: 1 + persistent_workers: true + multiprocessing_start_method: spawn +measurement: + warmup_passes: 1 + timed_passes_per_run: 1 + minimum_timed_seconds: 1.0 + output_contract: normalized-rgb + support_policy: common +execution: + per_run_subprocess: true + run_timeout_seconds: 3600 + checkpoint_each_run: true + maximum_memory_fraction: 0.6 diff --git a/examples/fodb-worker-followup-w16-native-axion.template.yaml b/examples/fodb-worker-followup-w16-native-axion.template.yaml new file mode 100644 index 0000000..7941537 --- /dev/null +++ b/examples/fodb-worker-followup-w16-native-axion.template.yaml @@ -0,0 +1,36 @@ +schema_version: "2.0" +experiment_name: fodb-worker-followup-w16-native-axion-WORKLOAD_ID +seed: 20260729 +repetitions: 5 +dataset: + descriptor: /data/datasets/PACKAGE_ID/package.json + package_id: PACKAGE_ID + workload_id: WORKLOAD_ID + manifest_id: MANIFEST_ID + selection: + method: all + expected_items: ITEM_COUNT + logical_repeat_factor: 1 +matrix: + decoders: + ajpegli: {threads: [default]} + pillow: {threads: [default]} + protocols: + loader-supply: + worker_profiles: + - workers: [16] + batch_size: 1 + prefetch_factor: 1 + persistent_workers: true + multiprocessing_start_method: spawn +measurement: + warmup_passes: 1 + timed_passes_per_run: 1 + minimum_timed_seconds: 1.0 + output_contract: normalized-rgb + support_policy: common +execution: + per_run_subprocess: true + run_timeout_seconds: 3600 + checkpoint_each_run: true + maximum_memory_fraction: 0.6 diff --git a/examples/fodb-worker-followup-w16-native-intel.template.yaml b/examples/fodb-worker-followup-w16-native-intel.template.yaml new file mode 100644 index 0000000..db54198 --- /dev/null +++ b/examples/fodb-worker-followup-w16-native-intel.template.yaml @@ -0,0 +1,42 @@ +schema_version: "2.0" +experiment_name: fodb-worker-followup-w16-native-intel-WORKLOAD_ID +seed: 20260729 +repetitions: 5 +dataset: + descriptor: /data/datasets/PACKAGE_ID/package.json + package_id: PACKAGE_ID + workload_id: WORKLOAD_ID + manifest_id: MANIFEST_ID + selection: + method: all + expected_items: ITEM_COUNT + logical_repeat_factor: 1 +matrix: + decoders: + ajpegli: {threads: [default]} + imageio: {threads: [default]} + kornia: {threads: [default]} + opencv: {threads: [1]} + pillow: {threads: [default]} + pyvips: {threads: [1]} + skimage: {threads: [default]} + torchvision: {threads: [1]} + protocols: + loader-supply: + worker_profiles: + - workers: [16] + batch_size: 1 + prefetch_factor: 1 + persistent_workers: true + multiprocessing_start_method: spawn +measurement: + warmup_passes: 1 + timed_passes_per_run: 1 + minimum_timed_seconds: 1.0 + output_contract: normalized-rgb + support_policy: common +execution: + per_run_subprocess: true + run_timeout_seconds: 3600 + checkpoint_each_run: true + maximum_memory_fraction: 0.6 diff --git a/examples/publication.template.yaml b/examples/publication.template.yaml new file mode 100644 index 0000000..7439197 --- /dev/null +++ b/examples/publication.template.yaml @@ -0,0 +1,8 @@ +schema_version: "2.0" +claim_scope: loader-supply +filters: + config.protocol_id: loader-supply + dataset.workload_id: WORKLOAD_ID +statistic: images_per_second +practical_margin_percent: 5 +generator_revision: "0000000000000000000000000000000000000000" diff --git a/gcp/run-many.sh b/gcp/run-many.sh index c6f3986..d51a590 100755 --- a/gcp/run-many.sh +++ b/gcp/run-many.sh @@ -1,58 +1,20 @@ #!/usr/bin/env bash -# run-many.sh — drive ./gcp/run.sh sequentially across a list of machine types -# with stockout retry and network-tolerant polling. -# -# Why this exists, given run.sh already has a polling loop: -# 1. run.sh's polling treats a failed `gcloud compute instances describe` -# as "VM disappeared" → exits 1. A 30s home-WiFi blip kills it. The -# VM keeps running in the cloud, finishes, self-deletes — but the -# local script is dead and the next iteration of a wrapper for-loop -# would launch a new VM possibly while the old one's quota is still -# held. With a 64 vCPU project quota that's still a real wedge. -# 2. GCP zonal stockouts (ZONE_RESOURCE_POOL_EXHAUSTED) happen randomly -# for c4-* / c4d-* / c4a-* — the modern families on hyperdisk-balanced -# live in a smaller capacity pool than c3 / n2d. We retry across zones -# automatically instead of you babysitting at 2am. -# 3. If run.sh fails to even create the VM (stockout, quota, image bug), -# there's nothing to poll — but a naive wrapper that extracts RUN_GCS -# from the banner before checking exit code will spin forever waiting -# for a DONE/FAILED that no VM will ever write. -# -# Usage: -# ./gcp/run-many.sh \ -# --machine-types "c4-standard-16 c3d-standard-16 c4a-standard-16" \ -# --imagenet-bucket gs://my-bucket/imagenet/val \ -# --results-bucket gs://my-bucket/imread-results \ -# [--zones "us-central1-a us-central1-c us-east5-b"] \ -# [--smoke] -# -# Anything not listed above is forwarded verbatim to ./gcp/run.sh. +# Run the same schema-2 campaign sequentially across machine types. set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -RUN_SH="$SCRIPT_DIR/run.sh" - -# ── Defaults ────────────────────────────────────────────────────────────────── MACHINE_TYPES="" -ZONES_FALLBACK="us-central1-a us-central1-c us-east5-b us-east1-b us-west1-a" -POLL_SECS=60 +ZONES="us-west4-a us-central1-a us-central1-c us-east5-b us-east1-b" PASSTHROUGH=() -# ── Argument parsing ────────────────────────────────────────────────────────── while [[ $# -gt 0 ]]; do case "$1" in --machine-types) MACHINE_TYPES="$2"; shift 2 ;; - --zones) ZONES_FALLBACK="$2"; shift 2 ;; - --poll-secs) POLL_SECS="$2"; shift 2 ;; - # `--zone X` from the user becomes the FIRST fallback zone — same - # mental model as run.sh ("try this zone first") without the - # downstream confusion of having two `--zone` flags. - --zone) ZONES_FALLBACK="$2 $ZONES_FALLBACK"; shift 2 ;; - # `--machine-type X` is the single-machine shortcut. - --machine-type) MACHINE_TYPES="$2"; shift 2 ;; + --machine-type) MACHINE_TYPES="$2"; shift 2 ;; + --zones) ZONES="$2"; shift 2 ;; -h|--help) - sed -n '2,30p' "$0" | sed 's/^# \?//' + echo "Usage: $0 --machine-types 'c4-standard-8 c4a-standard-8' [run.sh options]" exit 0 ;; *) PASSTHROUGH+=("$1"); shift ;; @@ -60,150 +22,40 @@ while [[ $# -gt 0 ]]; do done if [[ -z "$MACHINE_TYPES" ]]; then - echo "ERROR: --machine-types is required (space-separated list)." >&2 + echo "--machine-types is required" >&2 exit 2 fi -# ── Helpers ─────────────────────────────────────────────────────────────────── - -# extract_run_gcs -# Pull the RUN_GCS bucket path from a captured run.sh banner. Returns empty -# string if not found (which means VM creation failed before the banner -# reached the "Results:" line — handled by the caller). -extract_run_gcs() { - grep -oE 'gs://[^ ]+/imread-benchmark-[0-9-]+' "$1" 2>/dev/null \ - | head -n 1 -} - -# is_stockout -# True when the failure is "this zone is temporarily out of capacity for -# this machine type" — those are worth retrying in another zone. Quota -# errors / IAM errors / image errors are NOT — those need a human. -is_stockout() { - grep -qE 'ZONE_RESOURCE_POOL_EXHAUSTED|does not have enough resources' "$1" -} - -# poll_until_terminal -# Network-tolerant polling. All gcloud failures are silently swallowed — -# transient blips just become "another POLL_SECS of waiting" rather than a -# fatal local exit. Returns 0 on DONE, 1 on FAILED, 2 on timeout (24h). -poll_until_terminal() { - local run_gcs="$1" - local start_ts - start_ts=$(date +%s) - local timeout_secs=$((24 * 3600)) - - while true; do - sleep "$POLL_SECS" - local elapsed=$(( $(date +%s) - start_ts )) - if (( elapsed > timeout_secs )); then - echo " [poll] timed out after 24h — bailing." >&2 - return 2 - fi - - if gcloud --quiet storage objects describe "$run_gcs/DONE" \ - >/dev/null 2>&1; then - echo " [poll] DONE after $((elapsed / 60))m" - return 0 - fi - if gcloud --quiet storage objects describe "$run_gcs/FAILED" \ - >/dev/null 2>&1; then - echo " [poll] FAILED after $((elapsed / 60))m" - echo " $(gcloud --quiet storage cat "$run_gcs/FAILED" 2>/dev/null | head -1)" - return 1 - fi - - printf ' [poll] %dm elapsed, no terminal sentinel yet...\n' \ - "$((elapsed / 60))" - done -} - -# ── Main loop ───────────────────────────────────────────────────────────────── -overall_rc=0 - -for M in $MACHINE_TYPES; do - echo - echo "════════════════════════════════════════════════════════════════" - echo " Machine type: $M" - echo "════════════════════════════════════════════════════════════════" - - run_log="$(mktemp -t "imread-runlog-${M}.XXXXXX")" - trap 'rm -f "$run_log"' EXIT - - # ── Try each fallback zone until one boots a VM ─────────────────────── - booted_zone="" - for Z in $ZONES_FALLBACK; do - echo " → trying zone $Z..." - # Use --no-wait so creation is decoupled from polling. We do our - # OWN polling below with the network-tolerant loop. - if "$RUN_SH" \ - --machine-type "$M" \ - --zone "$Z" \ - --no-wait \ - "${PASSTHROUGH[@]}" 2>&1 | tee "$run_log"; then - booted_zone="$Z" +overall_status=0 +for machine_type in $MACHINE_TYPES; do + launched=false + for zone in $ZONES; do + log_file=$(mktemp -t "imread-${machine_type}.XXXXXX") + set +e + "$SCRIPT_DIR/run.sh" \ + --machine-type "$machine_type" \ + --zone "$zone" \ + "${PASSTHROUGH[@]}" 2>&1 | tee "$log_file" + status=${PIPESTATUS[0]} + set -e + if [[ $status -eq 0 ]]; then + launched=true + rm -f "$log_file" break fi - - # Distinguish "no capacity here, try next zone" from "real config bug" - if is_stockout "$run_log"; then - echo " ⚠ stockout in $Z, trying next zone..." + if grep -qE 'ZONE_RESOURCE_POOL_EXHAUSTED|does not have enough resources|does not exist in zone|not available in zone|Invalid value for field.*machineType' "$log_file"; then + echo "Machine unavailable for $machine_type in $zone; trying the next zone." + rm -f "$log_file" continue fi - echo " ✗ run.sh failed in $Z for non-stockout reason — skipping $M." >&2 - cat "$run_log" >&2 - overall_rc=1 + echo "Campaign failed for $machine_type in $zone." >&2 + rm -f "$log_file" + overall_status=1 break done - - if [[ -z "$booted_zone" ]]; then - echo " ✗ exhausted all zones for $M — skipping." >&2 - overall_rc=1 - continue - fi - - # ── Find the run's GCS path and poll ────────────────────────────────── - run_gcs="$(extract_run_gcs "$run_log")" - if [[ -z "$run_gcs" ]]; then - echo " ✗ could not parse RUN_GCS from run.sh output for $M — skipping poll." >&2 - overall_rc=1 - continue - fi - echo " RUN_GCS: $run_gcs" - echo " Polling for DONE/FAILED (every ${POLL_SECS}s, network-tolerant)..." - - # Don't let `set -e` kill the wrapper if poll returns FAILED or timeout — - # we want to fetch whatever partial results landed and move to the next - # machine type. - set +e - poll_until_terminal "$run_gcs" - poll_rc=$? - set -e - - # ── Fetch results regardless of DONE vs FAILED ──────────────────────── - # Even FAILED runs may have uploaded partial output/ from the ERR trap - # in vm_startup.sh (which flushes before writing the FAILED sentinel). - # rsync to ./output/ — corrects the nesting bug present in run.sh's - # original `cp --recursive`. - echo " Fetching results..." - if ! gcloud --quiet storage rsync --recursive \ - "$run_gcs/output/" "./output/"; then - echo " ⚠ rsync failed (network?). Retry later with:" >&2 - echo " gcloud storage rsync -r $run_gcs/output/ ./output/" >&2 - overall_rc=1 - fi - - if (( poll_rc != 0 )); then - overall_rc=1 + if [[ "$launched" != "true" ]]; then + overall_status=1 fi done -echo -echo "════════════════════════════════════════════════════════════════" -if (( overall_rc == 0 )); then - echo " All machines completed successfully." -else - echo " Completed with errors — see warnings above." -fi -echo "════════════════════════════════════════════════════════════════" -exit "$overall_rc" +exit "$overall_status" diff --git a/gcp/run.sh b/gcp/run.sh index d1789d2..1eb3942 100755 --- a/gcp/run.sh +++ b/gcp/run.sh @@ -1,357 +1,178 @@ #!/usr/bin/env bash -# gcp/run.sh — Launch a GCP VM, run imread benchmarks, fetch results, terminate VM. -# -# Usage: -# ./gcp/run.sh [options] -# -# Required (or set as env vars): -# --imagenet-bucket gs://bucket/path/to/imagenet/val (or IMAGENET_BUCKET) -# --results-bucket gs://bucket/imread-results (or RESULTS_BUCKET) -# -# Optional: -# --zone GCP zone (default: us-central1-a) -# --machine-type GCP machine type (default: c3-standard-8) -# --libs comma-separated decoder names, or all (default: all) -# --num-images images to benchmark (default: 50000 = full ImageNet val) -# --num-runs single-thread timed runs per library (default: 5) -# --dl-runs DataLoader timed runs per worker config (default: 3) -# --workers DataLoader worker counts to test (default: "0 2 4 8") -# --smoke short validation run on a new machine type -# (forces num-images=2000, num-runs=3, dl-runs=2, workers="0 2 8") -# --no-wait fire-and-forget; skip polling + fetch -# --no-cache disable venv caching for this run (cold install on the VM) -# --force-rebuild bypass cache LOOKUP and reupload a fresh tarball. -# Use to re-resolve PyPI without editing uv.lock — picks up -# newly released wheel versions of every cached library. -# --keep-on-failure keep the VM alive on ERR (instead of self-deleting) so -# you can SSH in and triage. Default: self-delete on any -# failure to avoid surprise billing on a stuck VM. -# --upload-imagenet path/to/local/imagenet/val one-time upload to --imagenet-bucket, then exit -# -# Examples: -# # One-time: upload ImageNet to GCS -# ./gcp/run.sh --upload-imagenet ~/imagenet/val --imagenet-bucket gs://my-bucket/imagenet/val -# -# # Smoke test on a new machine type (~10 min, ~$0.10) -# ./gcp/run.sh --machine-type c4-standard-16 --smoke --libs ajpegli \ -# --imagenet-bucket gs://YOUR_BUCKET/imagenet/val \ -# --results-bucket gs://YOUR_BUCKET/imread-results --no-wait -# -# # Full run (blocks ~45-90 min depending on machine, fetches results when done) -# ./gcp/run.sh \ -# --imagenet-bucket gs://YOUR_BUCKET/imagenet/val \ -# --results-bucket gs://YOUR_BUCKET/imread-results -# -# # Fire and forget -# ./gcp/run.sh --imagenet-bucket gs://... --results-bucket gs://... --no-wait +# Launch one ephemeral GCP worker for a schema-2 benchmark campaign. set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" -# ── Defaults ────────────────────────────────────────────────────────────────── -ZONE="${ZONE:-us-central1-a}" -MACHINE_TYPE="${MACHINE_TYPE:-c3-standard-8}" -# Statistics rationale (see docs/gcp_benchmarks.md "Sample size"): -# N=50000 → full ImageNet val. No sampling story to defend in the paper. -# NUM_RUNS=5 → enough for stable mean ± std. Per-image variance of decode -# time + N=50k already drives SE_mean below 0.1%, which is -# way below the 2x–100x effect sizes we actually compare. -# Going from 20 to 5 is a 4x wallclock reduction with zero -# paper-visible loss (we drop the per-run percentile columns). -# DL_RUNS=3 → DataLoader runs are dominated by worker startup variance; -# 5+ reps don't help the signal. -# WORKERS → dropped 1 (in-torch single-process is what 0 already -# measures; 0-vs-1 distinction is a torch impl detail nobody -# plots). Kept 0 (no-fork), 2 (fork sanity), 4, 8 (scaling). -NUM_IMAGES="${NUM_IMAGES:-50000}" -NUM_RUNS="${NUM_RUNS:-5}" -DL_RUNS="${DL_RUNS:-3}" -WORKERS="${WORKERS:-0 2 4 8}" -LIBS="${LIBS:-all}" -IMAGENET_BUCKET="${IMAGENET_BUCKET:-}" -RESULTS_BUCKET="${RESULTS_BUCKET:-}" +ZONE="${ZONE:-us-west4-a}" +MACHINE_TYPE="${MACHINE_TYPE:-c4-standard-16}" +BOOT_DISK_GB="${BOOT_DISK_GB:-150}" +DEPENDENCY_GROUPS="${IMREAD_DEPENDENCY_GROUPS:-mainstream}" +PLAN_PATH="" +DATASET_STORE="" +DATASET_DESCRIPTOR="" +RESULTS_STORE="" +ENVIRONMENT_STORE="" +DOWNLOAD_DIR="" NO_WAIT=false -SMOKE=false -NO_CACHE=false -FORCE_REBUILD=false KEEP_ON_FAILURE=false -UPLOAD_IMAGENET_LOCAL="" -# ── Argument parsing ────────────────────────────────────────────────────────── +usage() { + sed -n '2,34p' "$0" | sed 's/^# \?//' +} + while [[ $# -gt 0 ]]; do case "$1" in - --imagenet-bucket) IMAGENET_BUCKET="$2"; shift 2 ;; - --results-bucket) RESULTS_BUCKET="$2"; shift 2 ;; - --zone) ZONE="$2"; shift 2 ;; - --machine-type) MACHINE_TYPE="$2"; shift 2 ;; - --libs) LIBS="$2"; shift 2 ;; - --num-images) NUM_IMAGES="$2"; shift 2 ;; - --num-runs) NUM_RUNS="$2"; shift 2 ;; - --dl-runs) DL_RUNS="$2"; shift 2 ;; - --workers) WORKERS="$2"; shift 2 ;; - --no-wait) NO_WAIT=true; shift ;; - --smoke) SMOKE=true; shift ;; - --no-cache) NO_CACHE=true; shift ;; - --force-rebuild) FORCE_REBUILD=true; shift ;; - --keep-on-failure) KEEP_ON_FAILURE=true; shift ;; - --upload-imagenet) UPLOAD_IMAGENET_LOCAL="$2"; shift 2 ;; - -h|--help) - sed -n '2,45p' "$0" | sed 's/^# \?//' - exit 0 - ;; - *) echo "Unknown argument: $1"; exit 1 ;; + --plan) PLAN_PATH="$2"; shift 2 ;; + --dataset-store) DATASET_STORE="$2"; shift 2 ;; + --dataset-descriptor) DATASET_DESCRIPTOR="$2"; shift 2 ;; + --results-store) RESULTS_STORE="$2"; shift 2 ;; + --environment-store) ENVIRONMENT_STORE="$2"; shift 2 ;; + --groups) DEPENDENCY_GROUPS="$2"; shift 2 ;; + --zone) ZONE="$2"; shift 2 ;; + --machine-type) MACHINE_TYPE="$2"; shift 2 ;; + --boot-disk-gb) BOOT_DISK_GB="$2"; shift 2 ;; + --download-dir) DOWNLOAD_DIR="$2"; shift 2 ;; + --no-wait) NO_WAIT=true; shift ;; + --keep-on-failure) KEEP_ON_FAILURE=true; shift ;; + -h|--help) usage; exit 0 ;; + *) echo "Unknown argument: $1" >&2; exit 2 ;; esac done -# Smoke mode: smallest run that still validates every library + every -# distinct code path on a new machine type. -# NUM_IMAGES=200 → ~6 batches at bs=32, exercises the loop without burning time. -# NUM_RUNS=1 → "did the call succeed?". Stats need a real run. -# DL_RUNS=1 → same logic for DataLoader rounds. -# WORKERS="0 2" → 0 = no-fork path; 2 = fork path (catches pyvips-Arm-style -# deadlocks). 4/8 is just scaling, useless for smoke. -if [[ "$SMOKE" == "true" ]]; then - NUM_IMAGES=200 - NUM_RUNS=1 - DL_RUNS=1 - WORKERS="0 2" -fi - -# ── One-time upload subcommand ───────────────────────────────────────────────── -if [[ -n "$UPLOAD_IMAGENET_LOCAL" ]]; then - if [[ -z "$IMAGENET_BUCKET" ]]; then - echo "Error: --imagenet-bucket is required for --upload-imagenet" - exit 1 +for required in PLAN_PATH DATASET_STORE DATASET_DESCRIPTOR RESULTS_STORE; do + if [[ -z "${!required}" ]]; then + echo "Missing required option for $required" >&2 + exit 2 fi - echo "Uploading ImageNet from $UPLOAD_IMAGENET_LOCAL → $IMAGENET_BUCKET" - echo "This is a one-time operation; subsequent runs reuse the GCS copy." - # Prefer gcloud storage over gsutil (Google's migration path; avoids macOS multiprocessing noise). - gcloud storage cp --recursive "$UPLOAD_IMAGENET_LOCAL" "$IMAGENET_BUCKET" - echo "Upload complete: $IMAGENET_BUCKET" - exit 0 -fi - -# ── Validation ──────────────────────────────────────────────────────────────── -if [[ -z "$IMAGENET_BUCKET" ]]; then - echo "Error: --imagenet-bucket is required (or set IMAGENET_BUCKET env var)" - exit 1 -fi -if [[ -z "$RESULTS_BUCKET" ]]; then - echo "Error: --results-bucket is required (or set RESULTS_BUCKET env var)" - exit 1 +done +if [[ ! -f "$PLAN_PATH" ]]; then + echo "Plan does not exist: $PLAN_PATH" >&2 + exit 2 fi - -if ! gcloud auth list --filter=status:ACTIVE --format="value(account)" 2>/dev/null | grep -q "@"; then - echo "Error: no active gcloud account. Run: gcloud auth login" - exit 1 +if ! command -v gcloud >/dev/null 2>&1; then + echo "gcloud is required" >&2 + exit 127 fi +ENVIRONMENT_STORE="${ENVIRONMENT_STORE:-$RESULTS_STORE}" -if ! command -v gcloud &>/dev/null; then - echo "Error: gcloud not found. Install: brew install --cask gcloud-cli" - exit 1 -fi - -# ── Run setup ───────────────────────────────────────────────────────────────── -RUN_NAME="imread-benchmark-$(date +%Y%m%d-%H%M%S)" -RUN_GCS="${RESULTS_BUCKET%/}/${RUN_NAME}" -STARTUP_SCRIPT="$SCRIPT_DIR/vm_startup.sh" - -# Sibling cache bucket: same parent as the results bucket, but suffix `-cache` -# so it lives next to per-run artifacts without polluting them. Pre-built -# venvs land here keyed by (os, arch, hash-of-pyproject+uv.lock). -# Override with CACHE_BUCKET env var if you want a separate bucket. -CACHE_BUCKET_DEFAULT="${RESULTS_BUCKET%/*}/imread-cache" -CACHE_BUCKET="${CACHE_BUCKET:-$CACHE_BUCKET_DEFAULT}" -[[ "$NO_CACHE" == "true" ]] && CACHE_BUCKET="" +case "$MACHINE_TYPE" in + c4a-*) IMAGE_FAMILY=ubuntu-2404-lts-arm64; BOOT_DISK_TYPE=hyperdisk-balanced ;; + t2a-*) IMAGE_FAMILY=ubuntu-2404-lts-arm64; BOOT_DISK_TYPE=pd-balanced ;; + c4-*|c4d-*|n4-*) IMAGE_FAMILY=ubuntu-2404-lts-amd64; BOOT_DISK_TYPE=hyperdisk-balanced ;; + *) IMAGE_FAMILY=ubuntu-2404-lts-amd64; BOOT_DISK_TYPE=pd-ssd ;; +esac -echo "══════════════════════════════════════════════════════" -echo " imread benchmark cloud run" -echo " Run ID : $RUN_NAME" -echo " Machine : $MACHINE_TYPE ($ZONE)" -echo " ImageNet : $IMAGENET_BUCKET" -echo " Results : $RUN_GCS" -echo " Libraries : $LIBS" -echo " Num images : $NUM_IMAGES" -echo " Single runs : $NUM_RUNS" -echo " DataLoader : $DL_RUNS runs × workers=[$WORKERS]" -[[ "$SMOKE" == "true" ]] && echo " Mode : SMOKE TEST" -[[ -n "$CACHE_BUCKET" ]] && echo " Venv cache : $CACHE_BUCKET" || echo " Venv cache : DISABLED" -[[ "$FORCE_REBUILD" == "true" ]] && echo " Cache lookup : SKIPPED (--force-rebuild → fresh resolve, then re-upload)" -[[ "$KEEP_ON_FAILURE" == "true" ]] && echo " On failure : KEEP VM (--keep-on-failure → manual cleanup required)" -echo " Live log : gcloud storage cat $RUN_GCS/startup.log" -echo "══════════════════════════════════════════════════════" -echo +TEMP_ROOT=$(mktemp -d -t imread-gcp.XXXXXX) +cleanup_temp() { + rm -rf "$TEMP_ROOT" +} +trap cleanup_temp EXIT -# ── Pack and upload repo ────────────────────────────────────────────────────── -echo "[1/4] Packing repo..." -# BSD mktemp (macOS) only substitutes X's that are at the very END of the -# template. Passing `/tmp/imread-repo-XXXXXX.tar.gz` creates the LITERAL file, -# which then collides on the next run. Use a temp dir + fixed filename — the -# dir name is randomised, the contained filename can be anything we want. -REPO_TMPDIR=$(mktemp -d -t imread-repo.XXXXXX) -REPO_TARBALL="$REPO_TMPDIR/repo.tar.gz" -trap 'rm -rf "$REPO_TMPDIR"' EXIT cd "$REPO_ROOT" -# Capture working tree (committed + uncommitted + untracked-but-not-ignored) -# while excluding artifacts that aren't needed at run time. -# Listing files via git keeps us in sync with .gitignore for untracked files. -# The `[ -e "$f" ]` filter drops files that git still tracks but have been -# deleted-and-not-yet-committed locally — without it, tar bails out with -# "Cannot stat" the moment any tracked file is removed. -# Strip macOS-specific tar metadata before packing: -# COPYFILE_DISABLE=1 stops macOS from sneaking `._filename` AppleDouble -# companions into the archive (no-op on Linux). -# --no-xattrs drops com.apple.provenance + other xattrs Gatekeeper -# adds to network-downloaded files. Both BSD tar and -# GNU tar accept this flag, so the script is portable. -# Without these, GNU tar on the Linux VM spams startup.log with thousands of -# "Ignoring unknown extended header keyword 'LIBARCHIVE.xattr.com.apple.*'" -# warnings — harmless, but they drown out useful log lines and waste GCS -# log-bandwidth on the periodic sync. -{ git ls-files; git ls-files --others --exclude-standard; } \ - | grep -vE '^(output|gcp|docs|_internal|paper.*)/' \ - | while IFS= read -r f; do [ -e "$f" ] && printf '%s\n' "$f"; done \ - | COPYFILE_DISABLE=1 tar --no-xattrs -czf "$REPO_TARBALL" -T - +{ + git ls-files + git ls-files --others --exclude-standard +} | sort -u | grep -E '^(imread_benchmark/|scripts/|pyproject\.toml$|uv\.lock$|README\.md$|LICENSE)' > "$TEMP_ROOT/source-files.txt" + +RUNNER_REVISION=$(python3 - "$TEMP_ROOT/source-files.txt" <<'PY' +import hashlib +import pathlib +import sys + +digest = hashlib.sha256() +for line in pathlib.Path(sys.argv[1]).read_text().splitlines(): + path = pathlib.Path(line) + if not path.is_file(): + continue + digest.update(line.encode()) + digest.update(b"\0") + digest.update(hashlib.sha256(path.read_bytes()).digest()) + digest.update(b"\0") +print(digest.hexdigest()) +PY +) + +REPO_ARCHIVE="$TEMP_ROOT/repo.tar.gz" +COPYFILE_DISABLE=1 tar --no-xattrs -czf "$REPO_ARCHIVE" -T "$TEMP_ROOT/source-files.txt" +REPO_ARCHIVE_SHA256=$(shasum -a 256 "$REPO_ARCHIVE" | awk '{print $1}') +PLAN_SHA256=$(shasum -a 256 "$PLAN_PATH" | awk '{print $1}') +CONTROL_ROOT="${RESULTS_STORE%/}/control" +REPO_URI="$CONTROL_ROOT/sources/$RUNNER_REVISION/$REPO_ARCHIVE_SHA256.tar.gz" +PLAN_URI="$CONTROL_ROOT/plans/$PLAN_SHA256.yaml" + +upload_create_only() { + local source=$1 + local destination=$2 + if gcloud storage objects describe "$destination" >/dev/null 2>&1; then + return + fi + gcloud --quiet storage cp "$source" "$destination" --if-generation-match=0 +} -echo "[1/4] Uploading repo + startup script to GCS..." -gcloud --quiet storage cp "$REPO_TARBALL" "$RUN_GCS/repo.tar.gz" -gcloud --quiet storage cp "$STARTUP_SCRIPT" "$RUN_GCS/vm_startup.sh" -# Tarball is in $REPO_TMPDIR which the EXIT trap nukes. No manual rm needed. -echo "[1/4] Done." +upload_create_only "$REPO_ARCHIVE" "$REPO_URI" +upload_create_only "$PLAN_PATH" "$PLAN_URI" -# ── Auto-detect arch + disk type from machine type ──────────────────────────── -# Arm: -# c4a (Axion / Neoverse V2): arm64 image + hyperdisk-balanced -# t2a (Ampere Altra / Neoverse N1, older): arm64 image + pd-balanced -# (t2a predates hyperdisk-balanced and rejects it at create time) -# x86: -# c4, c4d, n4 (modern): hyperdisk-balanced -# c3, c3d, n2, n2d, e2 (older): pd-ssd -case "$MACHINE_TYPE" in - c4a-*) - IMAGE_FAMILY=ubuntu-2404-lts-arm64 - BOOT_DISK_TYPE=hyperdisk-balanced - ;; - t2a-*) - IMAGE_FAMILY=ubuntu-2404-lts-arm64 - BOOT_DISK_TYPE=pd-balanced - ;; - c4-*|c4d-*|n4-*) - IMAGE_FAMILY=ubuntu-2404-lts-amd64 - BOOT_DISK_TYPE=hyperdisk-balanced - ;; - *) - IMAGE_FAMILY=ubuntu-2404-lts-amd64 - BOOT_DISK_TYPE=pd-ssd - ;; -esac +RUN_NAME="imread-$(date +%Y%m%d-%H%M%S)-${RUNNER_REVISION:0:8}" +JOB_ROOT="${RESULTS_STORE%/}/jobs/$RUN_NAME" +KEEP_VALUE=0 +[[ "$KEEP_ON_FAILURE" == "true" ]] && KEEP_VALUE=1 +METADATA="results-store=$RESULTS_STORE,environment-store=$ENVIRONMENT_STORE,dataset-store=$DATASET_STORE,dataset-descriptor=$DATASET_DESCRIPTOR,plan-uri=$PLAN_URI,repo-uri=$REPO_URI,repo-sha256=$REPO_ARCHIVE_SHA256,runner-revision=$RUNNER_REVISION,dependency-groups=$DEPENDENCY_GROUPS,machine-type=$MACHINE_TYPE,location=$ZONE,job-root=$JOB_ROOT,keep-on-failure=$KEEP_VALUE" -echo " Image family : $IMAGE_FAMILY" -echo " Boot disk : $BOOT_DISK_TYPE" -echo +echo "Run : $RUN_NAME" +echo "Runner revision : $RUNNER_REVISION" +echo "Machine : $MACHINE_TYPE ($ZONE)" +echo "Dataset : $DATASET_STORE/$DATASET_DESCRIPTOR" +echo "Results : $RESULTS_STORE" +echo "Environment cache: $ENVIRONMENT_STORE" +echo "Groups : $DEPENDENCY_GROUPS" +echo "Live log : gcloud storage cat $JOB_ROOT/startup.log" -# ── Create VM ───────────────────────────────────────────────────────────────── -echo "[2/4] Creating VM $RUN_NAME..." gcloud compute instances create "$RUN_NAME" \ --zone="$ZONE" \ --machine-type="$MACHINE_TYPE" \ --image-family="$IMAGE_FAMILY" \ --image-project=ubuntu-os-cloud \ - --boot-disk-size=60GB \ + --boot-disk-size="${BOOT_DISK_GB}GB" \ --boot-disk-type="$BOOT_DISK_TYPE" \ - --metadata="results-bucket=$RUN_GCS,imagenet-bucket=$IMAGENET_BUCKET,libs=$LIBS,num-images=$NUM_IMAGES,num-runs=$NUM_RUNS,dl-runs=$DL_RUNS,workers=$WORKERS,repo-tarball=$RUN_GCS/repo.tar.gz,cache-bucket=$CACHE_BUCKET,force-rebuild=$FORCE_REBUILD,keep-on-failure=$([[ $KEEP_ON_FAILURE == true ]] && echo 1 || echo 0)" \ - --metadata-from-file=startup-script="$STARTUP_SCRIPT" \ + --metadata="$METADATA" \ + --metadata-from-file=startup-script="$SCRIPT_DIR/vm_startup.sh" \ --scopes=storage-rw,compute-rw \ --maintenance-policy=TERMINATE \ --no-restart-on-failure \ --quiet -echo "[2/4] VM created. It will self-terminate when benchmarks finish." -# ── Cleanup trap ────────────────────────────────────────────────────────────── -cleanup() { - echo - echo "Interrupted — deleting VM $RUN_NAME..." - gcloud compute instances delete "$RUN_NAME" --zone="$ZONE" --quiet 2>/dev/null || true - echo "VM deleted. Partial results (if any) may be at $RUN_GCS/output/" - exit 1 +delete_on_interrupt() { + gcloud compute instances delete "$RUN_NAME" --zone="$ZONE" --quiet >/dev/null 2>&1 || true + exit 130 } -trap cleanup INT TERM +trap delete_on_interrupt INT TERM -# ── Fire-and-forget mode ────────────────────────────────────────────────────── if [[ "$NO_WAIT" == "true" ]]; then - echo - echo "Fire-and-forget mode." - echo "The VM runs benchmarks, uploads results to GCS, and deletes itself." - echo "You can close this terminal or shut down your laptop — nothing runs locally." - echo - echo "Monitor progress : gcloud storage cat $RUN_GCS/startup.log" - echo "Check if done : gcloud storage objects describe $RUN_GCS/DONE" - echo "Fetch results : gcloud storage cp --recursive $RUN_GCS/output/ ./output/" + echo "VM is running asynchronously and will delete itself." exit 0 fi -# ── Poll for completion ─────────────────────────────────────────────────────── -echo "[3/4] Waiting for benchmarks to finish (polling every 30s)..." -echo " Live log: gcloud storage cat $RUN_GCS/startup.log" -echo -START_TS=$(date +%s) - while true; do - sleep 30 - - ELAPSED=$(( $(date +%s) - START_TS )) - ELAPSED_FMT="$(( ELAPSED / 3600 ))h $(( (ELAPSED % 3600) / 60 ))m" - - if gcloud storage objects describe "$RUN_GCS/DONE" &>/dev/null; then - echo "[$ELAPSED_FMT] DONE sentinel found — benchmarks complete." + sleep 20 + if gcloud storage objects describe "$JOB_ROOT/DONE.json" >/dev/null 2>&1; then + echo "Campaign worker completed." break fi - - # FAILED sentinel is written by the ERR trap in vm_startup.sh before - # self-delete. Treats explicit failures as terminal so we don't spin - # waiting for a VM that's about to vanish. - if gcloud storage objects describe "$RUN_GCS/FAILED" &>/dev/null; then - echo "[$ELAPSED_FMT] FAILED sentinel found — startup script crashed." - echo "Cause: $(gcloud --quiet storage cat "$RUN_GCS/FAILED" 2>/dev/null | head -1)" - echo "Logs : gcloud storage cat $RUN_GCS/startup.log" + if gcloud storage objects describe "$JOB_ROOT/FAILED.json" >/dev/null 2>&1; then + gcloud storage cat "$JOB_ROOT/FAILED.json" || true + echo "Campaign worker failed; see $JOB_ROOT/startup.log" >&2 exit 1 fi - - # VM self-deletes after writing DONE/FAILED. If it's gone without - # either, the VM was force-killed externally (e.g. preempted). - VM_STATUS=$(gcloud compute instances describe "$RUN_NAME" \ - --zone="$ZONE" --format="value(status)" 2>/dev/null || echo "GONE") - - if [[ "$VM_STATUS" == "GONE" ]]; then - if gcloud storage objects describe "$RUN_GCS/DONE" &>/dev/null; then - echo "[$ELAPSED_FMT] VM deleted itself after completing. DONE sentinel found." - break - fi - echo "[$ELAPSED_FMT] WARNING: VM is gone but no DONE/FAILED sentinel." - echo "Likely preempted or manually deleted. Check: gcloud storage cat $RUN_GCS/startup.log" + if ! gcloud compute instances describe "$RUN_NAME" --zone="$ZONE" >/dev/null 2>&1; then + echo "VM disappeared without a terminal marker; see $JOB_ROOT/startup.log" >&2 exit 1 fi - - echo "[$ELAPSED_FMT] Status: $VM_STATUS — still running..." done -# ── Fetch results ───────────────────────────────────────────────────────────── -# rsync (not `cp --recursive`) so the remote `output//` lands as -# `./output//`, not nested under `./output/output//`. -# `cp --recursive gs://.../output/ ./output/` copies the SOURCE directory as a -# subdirectory under DESTINATION, which produces the wrong nesting and silently -# scatters results across runs (every machine type ends up under output/output/). -echo "[4/4] Downloading results..." -mkdir -p "$REPO_ROOT/output" -gcloud storage rsync --recursive "$RUN_GCS/output/" "$REPO_ROOT/output/" -echo "[4/4] Results saved to: $REPO_ROOT/output/" - -echo -echo "══════════════════════════════════════════════════════" -echo " Run complete: $RUN_NAME" -echo " Results : $REPO_ROOT/output/" -echo " Full log : gcloud storage cat $RUN_GCS/startup.log" -echo " VM self-deleted after benchmarks finished." -echo "══════════════════════════════════════════════════════" +if [[ -n "$DOWNLOAD_DIR" ]]; then + mkdir -p "$DOWNLOAD_DIR" + gcloud storage rsync --recursive "$JOB_ROOT" "$DOWNLOAD_DIR/$RUN_NAME" +fi diff --git a/gcp/vm_startup.sh b/gcp/vm_startup.sh index 09a1e15..0dbf52a 100755 --- a/gcp/vm_startup.sh +++ b/gcp/vm_startup.sh @@ -1,433 +1,159 @@ #!/usr/bin/env bash -# gcp/vm_startup.sh — Runs automatically on the GCP VM at boot via instance metadata. -# DO NOT run this locally. It is uploaded to GCS and referenced as the startup-script. -# -# Flow: -# 1. Read config from GCP instance metadata -# 2. Install system deps + uv -# 3. Download repo tarball from GCS -# 4. Download ImageNet val from GCS to local disk -# 5. Run single-thread + default-thread benchmarks -# 6. Run DataLoader benchmarks -# 7. Upload results to GCS -# 8. Write DONE sentinel -# 9. Self-delete the VM instance (requires compute-rw scope) +# Minimal VM bootstrap. Benchmark semantics live in the schema-2 Python CLI. set -euo pipefail -# google-metadata-script-runner runs the script as root with a minimal env. -# HOME is not set, which breaks `set -u` when uv / other tools reference it. -export HOME="${HOME:-/root}" - -# Self-delete the VM via the GCP Compute REST API. -# Used by both the success path and the ERR trap so a failed run doesn't -# leave a $0.50/hr c4-standard-16 idling indefinitely. -# Requires --scopes=compute-rw on instance creation (set in gcp/run.sh). -self_delete_vm() { - local _meta="http://metadata.google.internal/computeMetadata/v1" - local _hdr="Metadata-Flavor: Google" - local _token _project _instance _zone - _token=$(curl -sf "$_meta/instance/service-accounts/default/token" -H "$_hdr" \ - | python3 -c 'import sys,json; print(json.load(sys.stdin)["access_token"])') || return 1 - _project=$(curl -sf "$_meta/project/project-id" -H "$_hdr") || return 1 - _instance=$(curl -sf "$_meta/instance/name" -H "$_hdr") || return 1 - _zone=$(curl -sf "$_meta/instance/zone" -H "$_hdr" | sed 's|.*/||') || return 1 - curl -sf -X DELETE \ - "https://compute.googleapis.com/compute/v1/projects/$_project/zones/$_zone/instances/$_instance" \ - -H "Authorization: Bearer $_token" \ - -o /dev/null -} - -# ERR trap: log the failing line, flush log + partial output to GCS, then -# self-delete so a broken run doesn't bill until manually noticed. -# Set FATAL_NOAUTODELETE=1 in metadata to keep the VM alive for SSH triage. -on_error() { - local _exit=$? - local _line=$1 - echo "ERROR: vm_startup.sh failed at line $_line (exit $_exit)" >&2 - # Best-effort flush — these may not be available yet during early-boot failures. - if command -v gcloud >/dev/null 2>&1 && [[ -n "${GCS_LOG:-}" ]]; then - gcloud --quiet storage cp "$LOG_FILE" "$GCS_LOG" >/dev/null 2>&1 || true - if [[ -d "${OUTPUT_DIR:-/nonexistent}" ]]; then - gcloud --quiet storage rsync --recursive \ - "$OUTPUT_DIR" "${GCS_OUTPUT:-$RESULTS_BUCKET/output}" >/dev/null 2>&1 || true - fi - # Sentinel so the launcher can distinguish failure from in-progress. - printf 'failed at line %s exit %s\n%s\n' "$_line" "$_exit" "$(date -u)" \ - | gcloud --quiet storage cp - "$RESULTS_BUCKET/FAILED" >/dev/null 2>&1 || true - fi - if [[ "${FATAL_NOAUTODELETE:-0}" != "1" ]]; then - self_delete_vm || true - fi - exit "$_exit" -} -trap 'on_error $LINENO' ERR +# GCS resume transfers can require many short-lived subprocesses. Keep their +# descriptor ceiling above the Ubuntu default and bypass the Snap launcher, +# whose per-command DBus scope creation is unreliable under that load. +ulimit -n 65535 +if [[ -x /snap/google-cloud-cli/current/bin/gcloud ]]; then + export PATH="/snap/google-cloud-cli/current/bin:$PATH" +fi -METADATA_URL="http://metadata.google.internal/computeMetadata/v1/instance/attributes" -META_HEADER="Metadata-Flavor: Google" +METADATA_ROOT="http://metadata.google.internal/computeMetadata/v1" +METADATA_HEADER="Metadata-Flavor: Google" meta() { - curl -sf "$METADATA_URL/$1" -H "$META_HEADER" + curl -sf "$METADATA_ROOT/instance/attributes/$1" -H "$METADATA_HEADER" } -# ── Read metadata ────────────────────────────────────────────────────────────── -RESULTS_BUCKET=$(meta results-bucket) -IMAGENET_BUCKET=$(meta imagenet-bucket) -LIBS=$(meta libs 2>/dev/null || echo "all") -NUM_IMAGES=$(meta num-images) -REPO_TARBALL=$(meta repo-tarball) -# Optional metadata with sensible defaults so older callers still work. -NUM_RUNS=$(meta num-runs 2>/dev/null || echo 20) -DL_RUNS=$(meta dl-runs 2>/dev/null || echo 5) -WORKERS=$(meta workers 2>/dev/null || echo "0 1 2 4 8") -CACHE_BUCKET=$(meta cache-bucket 2>/dev/null || echo "") -# `--force-rebuild` on the launcher → bypasses cache LOOKUP but still uploads -# a fresh tarball at the end. Use this to re-resolve PyPI without editing -# uv.lock (e.g. monthly "is anything faster now" sweeps). -FORCE_REBUILD=$(meta force-rebuild 2>/dev/null || echo "false") -# `--keep-on-failure` on the launcher → keep the VM alive on ERR so you can -# SSH in and triage. Default is to self-delete to avoid billing surprises. -FATAL_NOAUTODELETE=$(meta keep-on-failure 2>/dev/null || echo "0") -export FATAL_NOAUTODELETE - -LOG_FILE="/var/log/imread_benchmark.log" -GCS_LOG="$RESULTS_BUCKET/startup.log" +self_delete_vm() { + local token project instance zone + token=$(curl -sf "$METADATA_ROOT/instance/service-accounts/default/token" -H "$METADATA_HEADER" \ + | python3 -c 'import json,sys; print(json.load(sys.stdin)["access_token"])') + project=$(curl -sf "$METADATA_ROOT/project/project-id" -H "$METADATA_HEADER") + instance=$(curl -sf "$METADATA_ROOT/instance/name" -H "$METADATA_HEADER") + zone=$(curl -sf "$METADATA_ROOT/instance/zone" -H "$METADATA_HEADER" | sed 's|.*/||') + curl -sf -X DELETE \ + "https://compute.googleapis.com/compute/v1/projects/$project/zones/$zone/instances/$instance" \ + -H "Authorization: Bearer $token" -o /dev/null +} -# Redirect all output to a local log file (line-buffered). -# The lazy "one gsutil cp at EOF" approach doesn't give a live log; -# instead, spawn a background loop below that uploads the log every 15s. +RESULTS_STORE=$(meta results-store) +ENVIRONMENT_STORE=$(meta environment-store) +DATASET_STORE=$(meta dataset-store) +DATASET_DESCRIPTOR=$(meta dataset-descriptor) +PLAN_URI=$(meta plan-uri) +REPO_URI=$(meta repo-uri) +REPO_SHA256=$(meta repo-sha256) +RUNNER_REVISION=$(meta runner-revision) +DEPENDENCY_GROUPS=$(meta dependency-groups) +MACHINE_TYPE=$(meta machine-type) +LOCATION=$(meta location) +JOB_ROOT=$(meta job-root) +KEEP_ON_FAILURE=$(meta keep-on-failure) + +LOG_FILE=/var/log/imread-benchmark.log +ARTIFACT_ROOT=/opt/imread-job/artifacts +ATTEMPTS_ROOT=/opt/imread-job/attempts +mkdir -p "$ARTIFACT_ROOT" "$ATTEMPTS_ROOT" exec > >(stdbuf -oL tee -a "$LOG_FILE") 2>&1 -# Background log-sync: uploads the log to GCS every 15 seconds so the user -# can `gcloud storage cat ...` at any time and see recent progress. -# PATH may not yet include gcloud/gsutil during early boot — fall back gracefully. -( - while true; do - sleep 15 - if command -v gcloud >/dev/null 2>&1; then - gcloud --quiet storage cp "$LOG_FILE" "$GCS_LOG" >/dev/null 2>&1 || true - elif command -v gsutil >/dev/null 2>&1; then - gsutil -q cp "$LOG_FILE" "$GCS_LOG" 2>/dev/null || true - fi - done -) & -LOG_SYNC_PID=$! +sync_diagnostics() { + gcloud --quiet storage cp "$LOG_FILE" "$JOB_ROOT/startup.log" >/dev/null 2>&1 || true + if [[ -d "$ATTEMPTS_ROOT" ]]; then + gcloud --quiet storage rsync --recursive "$ATTEMPTS_ROOT" "$JOB_ROOT/attempts" >/dev/null 2>&1 || true + fi +} -# Background output-sync: pushes /opt/imread_benchmark/output/ to GCS every -# 30s. Without this, a hang/crash mid-run (e.g. pyvips fork-deadlock on Arm) -# loses every JSON we already produced and forces an SSH rescue. With it, -# the latest finished library is always recoverable. Uses `gcloud storage rsync` -# so we only ship newly-written files, not the whole tree every cycle. -OUTPUT_DIR="/opt/imread_benchmark/output" -GCS_OUTPUT="$RESULTS_BUCKET/output" ( while true; do - sleep 30 - if [[ -d "$OUTPUT_DIR" ]] && command -v gcloud >/dev/null 2>&1; then - gcloud --quiet storage rsync --recursive \ - "$OUTPUT_DIR" "$GCS_OUTPUT" >/dev/null 2>&1 || true - fi + sleep 15 + sync_diagnostics done ) & -OUTPUT_SYNC_PID=$! +SYNC_PID=$! -trap 'kill $LOG_SYNC_PID $OUTPUT_SYNC_PID 2>/dev/null || true' EXIT +on_error() { + local exit_code=$? + local line=$1 + set +e + sync_diagnostics + printf '{"exit_code":%s,"line":%s,"runner_revision":"%s","status":"failed"}\n' \ + "$exit_code" "$line" "$RUNNER_REVISION" \ + | gcloud --quiet storage cp - "$JOB_ROOT/FAILED.json" + kill "$SYNC_PID" >/dev/null 2>&1 || true + if [[ "$KEEP_ON_FAILURE" != "1" ]]; then + self_delete_vm || true + fi + exit "$exit_code" +} +trap 'on_error $LINENO' ERR +trap 'kill "$SYNC_PID" >/dev/null 2>&1 || true' EXIT -echo "═══════════════════════════════════════════════════" -echo " imread benchmark — VM startup" -echo " $(date -u)" -echo " Results bucket : $RESULTS_BUCKET" -echo " ImageNet : $IMAGENET_BUCKET" -echo " Libraries : $LIBS" -echo " Num images : $NUM_IMAGES" -echo " Repo tarball : $REPO_TARBALL" -echo "═══════════════════════════════════════════════════" +echo "Starting schema-2 campaign at $(date -u --iso-8601=seconds)" +echo "Runner revision: $RUNNER_REVISION" -# ── System deps ──────────────────────────────────────────────────────────────── -echo -echo "[step 1] Installing system dependencies..." export DEBIAN_FRONTEND=noninteractive - -# Wait for cloud-init / unattended-upgrades to release the apt lock. -# Without this, `apt-get update` races with boot-time updates and exits non-zero. -echo " Waiting for cloud-init to finish (releases apt lock)..." cloud-init status --wait || true - -# Wait until the dpkg lock is gone (belt-and-suspenders). -for i in $(seq 1 60); do - if ! fuser /var/lib/dpkg/lock-frontend >/dev/null 2>&1 \ - && ! fuser /var/lib/apt/lists/lock >/dev/null 2>&1; then - break - fi - echo " apt locked (attempt $i/60), sleeping 5s..." - sleep 5 +apt-get update -q +apt-get install -y -q curl git python3 python3-venv +curl -LsSf https://astral.sh/uv/install.sh | sh +export PATH="/root/.local/bin:$PATH" + +mkdir -p /opt/imread-source +gcloud --quiet storage cp "$REPO_URI" /tmp/repo.tar.gz +echo "$REPO_SHA256 /tmp/repo.tar.gz" | sha256sum --check --strict +tar -xzf /tmp/repo.tar.gz -C /opt/imread-source +cd /opt/imread-source + +scripts/install-libjpeg-turbo.sh +LIBJPEG_TURBO_BACKEND=$(/opt/libjpeg-turbo/bin/djpeg -version 2>&1) +export CPATH="/opt/libjpeg-turbo/include${CPATH:+:$CPATH}" +export LD_LIBRARY_PATH="/opt/libjpeg-turbo/lib64${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}" +export LIBRARY_PATH="/opt/libjpeg-turbo/lib64${LIBRARY_PATH:+:$LIBRARY_PATH}" +export PATH="/opt/libjpeg-turbo/bin:$PATH" +export PKG_CONFIG_PATH="/opt/libjpeg-turbo/lib64/pkgconfig${PKG_CONFIG_PATH:+:$PKG_CONFIG_PATH}" + +UV_PROJECT_ENVIRONMENT=/opt/imread-control uv sync --frozen --no-editable --no-group dev +CONTROL=/opt/imread-control/bin/imread-benchmark + +mkdir -p /opt/imread-job /data/datasets /opt/imread-environments +gcloud --quiet storage cp "$PLAN_URI" /opt/imread-job/experiment.yaml +"$CONTROL" dataset materialize "$DATASET_DESCRIPTOR" \ + --store "$DATASET_STORE" \ + --cache-root /data/datasets > /opt/imread-job/dataset.json +PACKAGE_DESCRIPTOR=$(python3 -c 'import json; print(json.load(open("/opt/imread-job/dataset.json"))["descriptor"])') + +"$CONTROL" platform capture \ + --output /opt/imread-job/platform.json \ + --cloud-provider gcp \ + --machine-type "$MACHINE_TYPE" \ + --location "$LOCATION" > /opt/imread-job/platform-command.json + +IFS=',' read -r -a GROUP_ARRAY <<< "$DEPENDENCY_GROUPS" +for dependency_group in "${GROUP_ARRAY[@]}"; do + group=$(echo "$dependency_group" | xargs) + [[ -n "$group" ]] + "$CONTROL" environment provision \ + --group "$group" \ + --runner-revision "$RUNNER_REVISION" \ + --project-root /opt/imread-source \ + --cache-root /opt/imread-environments \ + --python /usr/bin/python3 \ + --native-backend "libjpeg-turbo=$LIBJPEG_TURBO_BACKEND" \ + --remote-store "$ENVIRONMENT_STORE" > "/opt/imread-job/environment-$group.json" + ENVIRONMENT_DESCRIPTOR=$(python3 -c \ + "import json; print(json.load(open('/opt/imread-job/environment-$group.json'))['descriptor'])") + ENVIRONMENT_PYTHON=$(python3 -c \ + "import json; print(json.load(open('/opt/imread-job/environment-$group.json'))['python'])") + + "$ENVIRONMENT_PYTHON" -m imread_benchmark.cli campaign run /opt/imread-job/experiment.yaml \ + --package-descriptor "$PACKAGE_DESCRIPTOR" \ + --environment-descriptor "$ENVIRONMENT_DESCRIPTOR" \ + --platform-descriptor /opt/imread-job/platform.json \ + --artifact-root "$ARTIFACT_ROOT" \ + --attempts-root "$ATTEMPTS_ROOT" \ + --runner-revision "$RUNNER_REVISION" \ + --remote-store "$RESULTS_STORE" \ + --worker-python "$ENVIRONMENT_PYTHON" \ + > "/opt/imread-job/campaign-$group.json" + sync_diagnostics done -# Disable AppStream / dep11 / translation metadata before `apt-get update`. -# These are software-center descriptions for desktop installs — useless on -# a headless benchmark VM, AND they're the chronic source of "Hash Sum -# mismatch" errors when Ubuntu's security mirror serves a stale dep11 file -# (observed 2026-04-17). Without them, `update` is faster and more reliable. -rm -f /etc/apt/apt.conf.d/50appstream -cat > /etc/apt/apt.conf.d/99-headless <<'EOF' -APT::AppStream::Enabled "false"; -Acquire::Languages "none"; -APT::Install-Recommends "false"; -APT::Install-Suggests "false"; -EOF - -# Run apt without piping through tail — pipefail would mask the real error. -# `update` failures are tolerated (|| true) because Ubuntu mirrors hand out -# transiently-corrupt index files for non-critical components (dep11, -# translations) several times a week. The subsequent `install` will fail -# loudly with `set -e` if a *real* Packages index is missing. -# -# Use `-q` (one q) instead of `-qq` so per-package install lines stream to the -# log. With `-qq` the VM looks frozen for 1-3 min while dpkg is silently -# unpacking — indistinguishable from a hang in serial-console triage. -# -# Packages: -# libjpeg-turbo8-dev — jpeg4py is a Cython extension built from sdist; needs headers. -# libturbojpeg0-dev — provides libturbojpeg.so.0 + headers (PyTurboJPEG is -# ctypes-only and only needs the .so, but on noble the -# runtime-only package is `libturbojpeg`, not -# `libturbojpeg0`. The `0-dev` name is the transitional -# metapackage that pulls in the right .so reliably -# across Ubuntu releases — costs ~50 KB of headers, -# saves debugging package-rename surprises). -# curl/git/python3/zstd — control-plane essentials. -# -# We deliberately do NOT install: -# libvips-dev — pyvips uses the `pyvips-binary` PyPI wheel (CFFI API -# mode) for bundled libvips + deps. Saves ~150 transitive -# GLib/GTK packages and 1-3 min of apt time per cold boot. -# zlib1g-dev / build-essential — required only for compiling pillow-simd -# from sdist (no Linux wheels published since 2023). -# Pillow-SIMD was dropped from the benchmark in 2026-04 -# (abandoned upstream, <0.1% of Pillow's PyPI downloads, -# superseded by jpeg4py / simplejpeg / kornia-rs which -# ship maintained Linux wheels). -apt-get update -q || apt-get update -q || true -apt-get install -y -q \ - libjpeg-turbo8-dev \ - libturbojpeg0-dev \ - curl \ - git \ - python3 \ - python3-pip \ - zstd -echo "[step 1] Done." - -# ── uv ───────────────────────────────────────────────────────────────────────── -echo -echo "[step 2] Installing uv..." -curl -Ls https://astral.sh/uv/install.sh | sh -export PATH="$HOME/.local/bin:$PATH" -uv --version -echo "[step 2] Done." - -# ── Repo ─────────────────────────────────────────────────────────────────────── -echo -echo "[step 3] Downloading + extracting repo..." -gcloud --quiet storage cp "$REPO_TARBALL" /tmp/repo.tar.gz -mkdir -p /opt/imread_benchmark -tar -xzf /tmp/repo.tar.gz -C /opt/imread_benchmark -cd /opt/imread_benchmark -echo "[step 3] Done. Contents:" -ls -1 - -# ── ImageNet ─────────────────────────────────────────────────────────────────── -# Two paths, auto-selected: -# -# FAST: tarball at ".tar" exists → download single 5.5 GB blob -# + tar -xf locally. ~30 sec for any NUM_IMAGES, since tiny-file API -# overhead (50k × 50 ms = 40+ min) is replaced by one big sequential -# GET (5.5 GB at same-region GCS bandwidth = a few seconds). -# -# SLOW: per-file `gcloud storage cp -I` over the listed objects. -# Kept as fallback for buckets that don't have a tarball, and -# instinctively the "right" code path for partial downloads. -# -# Build the tarball once with: -# tar -cf val.tar -C ~/data/imagenet val -# gcloud storage cp val.tar gs:///path/imagenet/val.tar -# (See docs/gcp_benchmarks.md.) -echo -echo "[step 4] Materializing $NUM_IMAGES images at /data/imagenet/val ..." -IMAGENET_DIR=/data/imagenet/val -IMAGENET_PARENT=$(dirname "$IMAGENET_DIR") -mkdir -p "$IMAGENET_PARENT" - -BUCKET_PREFIX="${IMAGENET_BUCKET%/}" -TAR_PATH="${BUCKET_PREFIX}.tar" - -if gcloud storage objects describe "$TAR_PATH" >/dev/null 2>&1; then - echo " Fast path: $TAR_PATH" - # 2-step (cp + tar) instead of `cat | tar`. The streamed pipe variant - # works but `gcloud storage cat` of a multi-GB blob is less robust on - # transient TCP resets; the cp+tar combo is bulletproof and only adds - # ~5 sec of extra disk I/O on hyperdisk-balanced. - rm -rf "$IMAGENET_DIR" - time gcloud --quiet storage cp "$TAR_PATH" /tmp/val.tar - echo " Tarball size: $(du -h /tmp/val.tar | cut -f1)" - time tar -xf /tmp/val.tar -C "$IMAGENET_PARENT" - rm -f /tmp/val.tar - - # Trim down to NUM_IMAGES for smoke runs. Sort + head + delete the rest - # is faster than copying selected files to a new dir (~50k unlinks vs. - # 50k copies + 50k unlinks). `-z` keeps us safe for filenames with newlines. - TOTAL=$(find "$IMAGENET_DIR" -maxdepth 1 \( -iname '*.jpg' -o -iname '*.jpeg' \) | wc -l) - if [[ "$NUM_IMAGES" -lt "$TOTAL" ]]; then - echo " Trimming $TOTAL → $NUM_IMAGES (smoke / partial run)..." - find "$IMAGENET_DIR" -maxdepth 1 \( -iname '*.jpg' -o -iname '*.jpeg' \) -print0 \ - | sort -z \ - | tail -zn +"$((NUM_IMAGES + 1))" \ - | xargs -0 -r rm -f - fi -else - echo " Slow path: per-file download (no tarball at $TAR_PATH)." - echo " Build one to skip this: tar -cf val.tar val/ && gcloud storage cp val.tar $TAR_PATH" - mkdir -p "$IMAGENET_DIR" - - # Materialize the full sorted list to disk first, THEN take the first N. - # Doing `... | sort | head -n N` would SIGPIPE upstream stages once head - # closes stdin; with `set -o pipefail` that kills the whole script (exit 141). - gcloud --quiet storage ls "$BUCKET_PREFIX/**" \ - | grep -Ei '\.(jpe?g)$' \ - | sort > /tmp/imagenet_all.txt - head -n "$NUM_IMAGES" /tmp/imagenet_all.txt > /tmp/imagenet_files.txt - - WANTED=$(wc -l < /tmp/imagenet_files.txt) - echo " Selected $WANTED files; downloading in parallel..." - # `-I` reads source URIs from stdin; gcloud parallelizes downloads internally. - time gcloud --quiet storage cp -I "$IMAGENET_DIR/" < /tmp/imagenet_files.txt -fi - -IMAGE_COUNT=$(find "$IMAGENET_DIR" -maxdepth 1 \( -iname '*.jpg' -o -iname '*.jpeg' \) | wc -l) -echo "[step 4] Done. $IMAGE_COUNT JPEGs in $IMAGENET_DIR" - -# ── Venv cache pull ──────────────────────────────────────────────────────────── -# Skip the ~25-min `uv pip install` × 11 libs by pulling pre-built venvs from -# GCS, keyed by (os, arch, hash-of-pyproject+uv.lock). Cache is populated at the -# end of a successful run, so the first run on a new (machine_arch, deps) combo -# is full-cost; every subsequent run reuses it in seconds. -echo -echo "[cache] Computing venv cache key..." -CACHE_HIT=false - -if [[ -n "$CACHE_BUCKET" ]]; then - # Hash ONLY uv.lock — it already captures the full resolved closure - # (every package, version, hash, extra, index). Including pyproject.toml - # would also bust the cache on cosmetic edits (pyproject-fmt reordering, - # README link, ruff config tweak) that don't change a single installed - # byte. New library versions are picked up the moment uv.lock changes. - REQ_HASH=$(sha256sum uv.lock 2>/dev/null | cut -c1-12) - CACHE_KEY="venvs-$(uname -s)-$(uname -m)-${REQ_HASH}.tar.zst" - CACHE_PATH="${CACHE_BUCKET%/}/${CACHE_KEY}" - echo "[cache] Key : $CACHE_KEY" - echo "[cache] Path : $CACHE_PATH" - - if [[ "$FORCE_REBUILD" == "true" ]]; then - echo "[cache] FORCE_REBUILD=true — bypassing lookup, will rebuild + reupload." - elif gcloud storage objects describe "$CACHE_PATH" >/dev/null 2>&1; then - echo "[cache] HIT — downloading + extracting venvs..." - time gcloud --quiet storage cp "$CACHE_PATH" /tmp/venvs.tar.zst - time tar --use-compress-program='zstd -d' -xf /tmp/venvs.tar.zst - rm -f /tmp/venvs.tar.zst - CACHE_HIT=true - echo "[cache] Restored $(find venvs -mindepth 1 -maxdepth 1 -type d | wc -l) venvs." - else - echo "[cache] MISS — will build venvs and populate cache after the run." - fi -else - echo "[cache] DISABLED (no cache-bucket metadata)" -fi - -# ── Control-plane venv (just to get the `imread-benchmark` CLI) ──────────────── -# A small venv (numpy + pandas + typer) that drives the per-group worker venvs. -# Worker venvs (mainstream / tensorflow) are created lazily by the CLI. -# Idempotent: cached `venvs/control/` from a previous run is reused as-is, since -# `uv pip install -e .` is fast on a populated venv and picks up any code changes. -echo -echo "[step 5] Setting up control-plane venv..." -if [[ ! -x venvs/control/bin/python ]]; then - uv venv venvs/control --python python3 --seed -fi -# shellcheck source=/dev/null -source venvs/control/bin/activate -UV_LINK_MODE=copy uv pip install -e . -echo "[step 5] Done." - -# ── Run benchmarks via the unified CLI ───────────────────────────────────────── -# `imread-benchmark run --mode both` orchestrates: -# - venv setup per group (mainstream / tensorflow) -# - single + default-thread benchmark for each lib -# - DataLoader benchmark for each lib eligible on this platform -# Platform skips (jpeg4py off macOS, pyvips off Arm-Linux DataLoader, etc.) are -# encoded on the decoder classes themselves, no shell `case` games. -echo -echo "[step 6] Running benchmarks..." -echo " LIBS=$LIBS NUM_IMAGES=$NUM_IMAGES NUM_RUNS=$NUM_RUNS DL_RUNS=$DL_RUNS" -echo " WORKERS=$WORKERS" -WORKERS_CSV=$(echo "$WORKERS" | tr ' ' ',') -# Partial-failure policy: per-decoder failures (e.g. turbojpeg crashing on a -# CMYK JPEG in ImageNet val) MUST NOT mark this VM as failed. We've lost -# whole 4-hour runs that way — 9 of 12 decoders had clean numbers but the ERR -# trap nuked the FAILED sentinel and the wrapper script bailed. -# -# The CLI now exits 0 on partial failure and writes -# output//run_summary.json with per-decoder status. A FAILED sentinel -# from this script means infrastructure broke (apt, venv, ImageNet download), -# not "one of 12 libraries had a hiccup". -imread-benchmark run \ - --data-dir "$IMAGENET_DIR" \ - --output-dir output \ - --libs "$LIBS" \ - --mode both \ - --num-images "$NUM_IMAGES" \ - --num-runs "$NUM_RUNS" \ - --dataloader-runs "$DL_RUNS" \ - --workers "$WORKERS_CSV" -echo "[step 6] Done." - -# ── Upload results ───────────────────────────────────────────────────────────── -echo -echo "[step 7] Final flush of results to $RESULTS_BUCKET/output/ ..." -# The background rsync above ships results every 30s, so this is mostly -# a no-op. Keeping it as a last-mile guarantee in case a JSON was written -# in the final 30s window before completion. -gcloud --quiet storage rsync --recursive output "$RESULTS_BUCKET/output" -echo "[step 7] Done." - -# ── Venv cache push (only on cold-cache run) ─────────────────────────────────── -# Tar venvs/ and upload so the next run on this (os, arch, deps) combo -# can skip ~25 min of `uv pip install`. Skip if we hit the cache (no point -# re-uploading what we already pulled). -if [[ -n "$CACHE_BUCKET" && "$CACHE_HIT" != "true" ]]; then - echo - echo "[cache] Populating venv cache at $CACHE_PATH ..." - # zstd -3 = good speed/ratio tradeoff. -T0 = use all cores. - time tar --use-compress-program='zstd -3 -T0' -cf /tmp/venvs.tar.zst venvs/ - echo "[cache] Tarball size: $(du -h /tmp/venvs.tar.zst | cut -f1)" - time gcloud --quiet storage cp /tmp/venvs.tar.zst "$CACHE_PATH" - rm -f /tmp/venvs.tar.zst - echo "[cache] Done." -fi - -# ── DONE sentinel ────────────────────────────────────────────────────────────── -echo -echo "[step 8] Writing DONE sentinel..." -date -u | gcloud --quiet storage cp - "$RESULTS_BUCKET/DONE" -echo "[step 8] Done." - -echo -echo "═══════════════════════════════════════════════════" -echo " All benchmarks complete. $(date -u)" -echo " Deleting this VM instance." -echo "═══════════════════════════════════════════════════" - -# Flush log to GCS one final time before self-deletion -sleep 2 -gcloud --quiet storage cp "$LOG_FILE" "$GCS_LOG" >/dev/null 2>&1 || true - +sync_diagnostics +printf '{"runner_revision":"%s","status":"complete"}\n' "$RUNNER_REVISION" \ + | gcloud --quiet storage cp - "$JOB_ROOT/DONE.json" +kill "$SYNC_PID" >/dev/null 2>&1 || true self_delete_vm diff --git a/imread_benchmark/analysis/__init__.py b/imread_benchmark/analysis/__init__.py new file mode 100644 index 0000000..b6faee7 --- /dev/null +++ b/imread_benchmark/analysis/__init__.py @@ -0,0 +1,25 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING, Any + +if TYPE_CHECKING: + from imread_benchmark.analysis.canonical import RunBundleRecord + from imread_benchmark.analysis.claims import ClaimScope + +__all__ = ["ClaimScope", "RunBundleRecord", "assert_claim_scope", "load_bundles", "publish"] + + +def __getattr__(name: str) -> Any: + if name in {"RunBundleRecord", "load_bundles"}: + from imread_benchmark.analysis import canonical + + return getattr(canonical, name) + if name in {"ClaimScope", "assert_claim_scope"}: + from imread_benchmark.analysis import claims + + return getattr(claims, name) + if name == "publish": + from imread_benchmark.analysis.publication import publish + + return publish + raise AttributeError(name) diff --git a/imread_benchmark/analysis/canonical.py b/imread_benchmark/analysis/canonical.py new file mode 100644 index 0000000..d1c1cbd --- /dev/null +++ b/imread_benchmark/analysis/canonical.py @@ -0,0 +1,99 @@ +from __future__ import annotations + +import json +from dataclasses import dataclass +from pathlib import Path +from typing import Any + +from imread_benchmark.artifacts import RunSample, validate_run_bundle + + +@dataclass(frozen=True, slots=True) +class RunBundleRecord: + path: Path + run_key: str + bundle_id: str + config: dict[str, Any] + dataset: dict[str, Any] + environment: dict[str, Any] + platform: dict[str, Any] + runtime: dict[str, Any] + samples: tuple[RunSample, ...] + failures: tuple[dict[str, Any], ...] + events: tuple[dict[str, Any], ...] + summary: dict[str, Any] + bundle_manifest: dict[str, Any] + + +def load_bundles(root: str | Path) -> tuple[RunBundleRecord, ...]: + root_path = Path(root).resolve() + runs_root = root_path if root_path.name == "runs" else root_path / "runs" + records: list[RunBundleRecord] = [] + for marker in sorted(runs_root.glob("*/COMMITTED.json")): + bundle = marker.parent + run_key = bundle.name + validate_run_bundle(bundle, expected_run_key=run_key) + commit = _read_object(marker) + records.append( + RunBundleRecord( + path=bundle, + run_key=run_key, + bundle_id=_required_string(commit, "bundle_id"), + config=_read_object(bundle / "config.json"), + dataset=_read_object(bundle / "dataset.json"), + environment=_read_object(bundle / "environment.json"), + platform=_read_object(bundle / "platform.json"), + runtime=_read_object(bundle / "runtime.json"), + samples=tuple(_sample(row) for row in _read_jsonl(bundle / "samples.jsonl")), + failures=tuple(_read_jsonl(bundle / "failures.jsonl")), + events=tuple(_read_jsonl(bundle / "events.jsonl")), + summary=_read_object(bundle / "summary.json"), + bundle_manifest=_read_object(bundle / "bundle_manifest.json"), + ), + ) + return tuple(records) + + +def _sample(row: dict[str, Any]) -> RunSample: + sample_index = row.get("sample_index") + elapsed = row.get("elapsed_seconds") + items = row.get("items_processed") + started = row.get("started_at_utc") + if isinstance(sample_index, bool) or not isinstance(sample_index, int): + raise TypeError("sample_index must be an integer") + if isinstance(elapsed, bool) or not isinstance(elapsed, (int, float)): + raise TypeError("elapsed_seconds must be numeric") + if isinstance(items, bool) or not isinstance(items, int): + raise TypeError("items_processed must be an integer") + if started is not None and not isinstance(started, str): + raise TypeError("started_at_utc must be a string or null") + return RunSample( + sample_index=sample_index, + elapsed_seconds=float(elapsed), + items_processed=items, + started_at_utc=started, + ) + + +def _read_object(path: Path) -> dict[str, Any]: + value = json.loads(path.read_text()) + if not isinstance(value, dict): + raise TypeError(f"{path} must contain a JSON object") + return value + + +def _read_jsonl(path: Path) -> list[dict[str, Any]]: + rows: list[dict[str, Any]] = [] + for line_number, line in enumerate(path.read_text().splitlines(), start=1): + value = json.loads(line) + if not isinstance(value, dict): + raise TypeError(f"{path}:{line_number} must contain a JSON object") + rows.append(value) + return rows + + +def _required_string(payload: dict[str, Any], key: str) -> str: + value = payload.get(key) + if not isinstance(value, str) or not value: + raise TypeError(f"field {key!r} must be a non-empty string") + return value diff --git a/imread_benchmark/analysis/claims.py b/imread_benchmark/analysis/claims.py new file mode 100644 index 0000000..812b738 --- /dev/null +++ b/imread_benchmark/analysis/claims.py @@ -0,0 +1,27 @@ +from __future__ import annotations + +from enum import StrEnum +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from collections.abc import Sequence + + from imread_benchmark.analysis.canonical import RunBundleRecord + + +class ClaimScope(StrEnum): + DECODER_CAPACITY = "decoder-capacity" + LOADER_SUPPLY = "loader-supply" + TRAINING = "training" + + +def assert_claim_scope(records: Sequence[RunBundleRecord], scope: ClaimScope) -> None: + if not records: + raise ValueError("claim evidence set must not be empty") + protocols = {record.config.get("protocol_id") for record in records} + if scope is ClaimScope.TRAINING: + raise ValueError("training claims require end-to-end training evidence; decoder bundles are insufficient") + if scope is ClaimScope.DECODER_CAPACITY and protocols != {"decode-memory"}: + raise ValueError("decoder-capacity claims require only decode-memory bundles") + if scope is ClaimScope.LOADER_SUPPLY and protocols != {"loader-supply"}: + raise ValueError(f"{scope.value} claims require only loader-supply bundles") diff --git a/imread_benchmark/analysis/fodb_paper.py b/imread_benchmark/analysis/fodb_paper.py new file mode 100644 index 0000000..f055409 --- /dev/null +++ b/imread_benchmark/analysis/fodb_paper.py @@ -0,0 +1,1353 @@ +from __future__ import annotations + +import argparse +import hashlib +import json +import statistics +import tarfile +from collections import Counter, defaultdict +from dataclasses import dataclass +from pathlib import Path +from typing import Any + +from imread_benchmark.analysis.canonical import RunBundleRecord, load_bundles +from imread_benchmark.analysis.claims import ClaimScope, assert_claim_scope + +NATIVE_PLAN_ID = "6c746a5c21978f75c58c8c779f6628c2645a199b63489da9b9f1a9bb4f335c8f" +MIXED_PLAN_ID = "e04f54dc177cadf86cb99a9fc34b950afb5b1abee5b46af040bafc8a4aa7910b" +WORKER12_ALL_NATIVE_PLAN_ID = "4af3621c0fc5b75525a54eb4bc7171cb5399938b8515ea2c920a8a5e799d89ed" +WORKER12_ALL_MIXED_PLAN_ID = "1ad7306a07a7d23a602a5a74230e2d4edf5a0aaa55a61184fceb4718b64496f9" +WORKER12_INTEL_NATIVE_PLAN_ID = "c2f9038c67be2282d9aa15511bb57e93f1e9f80e86c392d77aaa6ddcd3d4cf54" +WORKER12_ZEN4_NATIVE_PLAN_ID = "a0e63ca15064755badb98b9b466eda3f9716f39f1515fae5e0b65fb74e6ade74" +WORKER16_MIXED_PLAN_ID = "eeef8895e513b6051c641ec49b97cea3d7878b08b0a2677d519eb2c70310f5e9" +WORKER16_INTEL_NATIVE_PLAN_ID = "4e5cba9f0d95976fd11638cbc71a22d018f044cb80cc89057cefd25ca5180f68" +WORKER16_AXION_NATIVE_PLAN_ID = "e246e31630e83ec09bf77c2f728c5ae3169d8717b630326e3ef568e418db193f" +RUNNER_REVISION = "52a0bea5a2f44079883c9a41472b3add285955f2b471c37674ddd2fb5d4da6ff" + +BASE_PLAN_WORKLOADS = {NATIVE_PLAN_ID: "fodb-native", MIXED_PLAN_ID: "fodb-mixed"} +WORKER12_PLAN_WORKLOADS = { + WORKER12_ALL_NATIVE_PLAN_ID: "fodb-native", + WORKER12_ALL_MIXED_PLAN_ID: "fodb-mixed", + WORKER12_INTEL_NATIVE_PLAN_ID: "fodb-native", + WORKER12_ZEN4_NATIVE_PLAN_ID: "fodb-native", +} +WORKER16_PLAN_WORKLOADS = { + WORKER16_MIXED_PLAN_ID: "fodb-mixed", + WORKER16_INTEL_NATIVE_PLAN_ID: "fodb-native", + WORKER16_AXION_NATIVE_PLAN_ID: "fodb-native", +} +PLAN_WORKLOADS = BASE_PLAN_WORKLOADS | WORKER12_PLAN_WORKLOADS | WORKER16_PLAN_WORKLOADS +EXPECTED_MACHINES = ( + "c4-standard-16", + "c3d-standard-16", + "c4d-standard-16", + "c4a-standard-16", +) +PLATFORM_LABELS = { + "c4-standard-16": "Intel 8581C", + "c3d-standard-16": "AMD Zen 4", + "c4d-standard-16": "AMD Zen 5", + "c4a-standard-16": "Axion", +} +EXPECTED_REPETITIONS = tuple(range(5)) +BASE_WORKERS = (0, 2, 4, 8) +WORKER12_DECISION_GRID = (*BASE_WORKERS, 12) +WORKER_GRID = (*WORKER12_DECISION_GRID, 16) +EXPECTED_ITEM_COUNTS = {"fodb-native": 324, "fodb-mixed": 1944} +EXPECTED_TIMED_ITEM_COUNTS = {"fodb-native": 324, "fodb-mixed": 1668} +CONTROLLED_THREADS = {"opencv": 1, "pyvips": 1, "torchvision": 1} +ALL_DECODERS = ( + "ajpegli", + "imagecodecs", + "imageio", + "jpeg4py", + "kornia", + "opencv", + "pillow", + "pyvips", + "simplejpeg", + "skimage", + "torchvision", + "turbojpeg", +) +WORKER12_DECODERS = { + **{("fodb-mixed", machine_type): ALL_DECODERS for machine_type in EXPECTED_MACHINES}, + ("fodb-native", "c4-standard-16"): ( + "ajpegli", + "imageio", + "kornia", + "opencv", + "pillow", + "pyvips", + "skimage", + "torchvision", + ), + ("fodb-native", "c3d-standard-16"): ( + "ajpegli", + "imageio", + "opencv", + "pillow", + "pyvips", + "skimage", + "torchvision", + ), + ("fodb-native", "c4d-standard-16"): ALL_DECODERS, + ("fodb-native", "c4a-standard-16"): ALL_DECODERS, +} +WORKER12_PLAN_SCOPES: dict[str, dict[tuple[str, str], tuple[str, ...]]] = { + WORKER12_ALL_NATIVE_PLAN_ID: { + ("fodb-native", "c4d-standard-16"): ALL_DECODERS, + ("fodb-native", "c4a-standard-16"): ALL_DECODERS, + }, + WORKER12_ALL_MIXED_PLAN_ID: {("fodb-mixed", machine_type): ALL_DECODERS for machine_type in EXPECTED_MACHINES}, + WORKER12_INTEL_NATIVE_PLAN_ID: { + ("fodb-native", "c4-standard-16"): WORKER12_DECODERS[("fodb-native", "c4-standard-16")], + }, + WORKER12_ZEN4_NATIVE_PLAN_ID: { + ("fodb-native", "c3d-standard-16"): WORKER12_DECODERS[("fodb-native", "c3d-standard-16")], + }, +} +WORKER16_DECODERS = { + **{("fodb-mixed", machine_type): ALL_DECODERS for machine_type in EXPECTED_MACHINES}, + ("fodb-native", "c4-standard-16"): WORKER12_DECODERS[("fodb-native", "c4-standard-16")], + ("fodb-native", "c4a-standard-16"): ("ajpegli", "pillow"), + ("fodb-native", "c3d-standard-16"): (), + ("fodb-native", "c4d-standard-16"): (), +} +WORKER16_PLAN_SCOPES: dict[str, dict[tuple[str, str], tuple[str, ...]]] = { + WORKER16_MIXED_PLAN_ID: {("fodb-mixed", machine_type): ALL_DECODERS for machine_type in EXPECTED_MACHINES}, + WORKER16_INTEL_NATIVE_PLAN_ID: { + ("fodb-native", "c4-standard-16"): WORKER16_DECODERS[("fodb-native", "c4-standard-16")], + }, + WORKER16_AXION_NATIVE_PLAN_ID: { + ("fodb-native", "c4a-standard-16"): WORKER16_DECODERS[("fodb-native", "c4a-standard-16")], + }, +} +FOLLOWUP_PLAN_SCOPES = WORKER12_PLAN_SCOPES | WORKER16_PLAN_SCOPES +FOLLOWUP_PLAN_WORKERS = dict.fromkeys(WORKER12_PLAN_SCOPES, 12) | dict.fromkeys(WORKER16_PLAN_SCOPES, 16) +WORKER16_MINIMUM_GAIN = 0.05 +PRIMARY_DECODERS = ("pillow", "opencv", "simplejpeg", "torchvision") +PRACTICAL_MARGIN = 0.10 +COMPATIBILITY_AUDIT_PACKAGE_ID = "b797eb3938cad77e115b2315b41457c5d6b3062968f7529ee35d1b38dd87eb2f" +COMPATIBILITY_AUDIT_MANIFEST_ID = "47f437595cf8a3215deb958f7278d63bcc6b2a00d4d595d821351431ceda803f" +COMPATIBILITY_AUDIT_EMPTY_DHT_ITEM_COUNT = 276 +COMPATIBILITY_AUDIT_EMPTY_DHT_SUCCESSES = { + "ajpegli": 0, + "imagecodecs": 276, + "imageio": 276, + "jpeg4py": 276, + "kornia": 276, + "opencv": 276, + "pillow": 276, + "pyvips": 276, + "simplejpeg": 276, + "skimage": 276, + "torchvision": 276, + "turbojpeg": 276, +} +COMPATIBILITY_AUDIT_FOUR_COMPONENT_ITEM_COUNT = 1 +COMPATIBILITY_AUDIT_FOUR_COMPONENT_SUCCESSES = { + "ajpegli": 0, + "imagecodecs": 0, + "imageio": 0, + "jpeg4py": 0, + "kornia": 0, + "opencv": 1, + "pillow": 1, + "pyvips": 0, + "simplejpeg": 1, + "skimage": 0, + "torchvision": 0, + "turbojpeg": 0, +} +COMPATIBILITY_AUDIT_ITEM_COUNT = ( + COMPATIBILITY_AUDIT_EMPTY_DHT_ITEM_COUNT + COMPATIBILITY_AUDIT_FOUR_COMPONENT_ITEM_COUNT +) +COMPATIBILITY_AUDIT_SUCCESSES = { + decoder: successes + COMPATIBILITY_AUDIT_FOUR_COMPONENT_SUCCESSES[decoder] + for decoder, successes in COMPATIBILITY_AUDIT_EMPTY_DHT_SUCCESSES.items() +} + + +class PaperAssetError(RuntimeError): + pass + + +@dataclass(frozen=True, slots=True) +class Measurement: + workload: str + machine_type: str + protocol: str + decoder: str + requested_threads: int | None + workers: int | None + repetition: int + images_per_second: float + run_key: str + bundle_id: str + + @property + def configuration_label(self) -> str: + thread_label = "default" if self.requested_threads is None else str(self.requested_threads) + return f"{self.decoder}[threads={thread_label}]" + + +@dataclass(frozen=True, slots=True) +class Aggregate: + workload: str + machine_type: str + protocol: str + decoder: str + requested_threads: int | None + workers: int | None + repetitions: tuple[int, ...] + raw_run_means: tuple[float, ...] + mean: float + sample_std: float + + @property + def configuration_label(self) -> str: + thread_label = "default" if self.requested_threads is None else str(self.requested_threads) + return f"{self.decoder}[threads={thread_label}]" + + +def build_paper_assets(*, artifact_root: Path, package_path: Path, output_root: Path) -> dict[str, Any]: + records = tuple(record for record in load_bundles(artifact_root) if record.config.get("plan_id") in PLAN_WORKLOADS) + package = _read_object(package_path) + measurements = _validate_and_measure(records, package) + aggregates = _aggregate(measurements) + manifests = _load_package_manifests(package_path, package) + support_item_ids = _support_item_ids(records) + workload_descriptors = _workload_descriptors(package, manifests, support_item_ids) + decisions = _decision_rows(aggregates) + thread_rows = _thread_rows(aggregates) + worker_transfer_rows = _worker_transfer_rows(aggregates) + worker16_candidates = _worker16_candidate_rows(aggregates) + compatibility_audit = _compatibility_audit() + sections = { + "compatibility_audit": compatibility_audit, + "decisions": decisions, + "thread_controls": thread_rows, + "worker_transfer": worker_transfer_rows, + "worker16_candidates": worker16_candidates, + "workloads": workload_descriptors, + } + evidence = _evidence_document(records, measurements, aggregates, sections) + + generated_dir = output_root / "generated" + figures_dir = output_root / "figures" + generated_dir.mkdir(parents=True, exist_ok=True) + figures_dir.mkdir(parents=True, exist_ok=True) + _write_json(generated_dir / "fodb_evidence.json", evidence) + _write_json(generated_dir / "fodb_worker16_candidates.json", worker16_candidates) + _write_text(generated_dir / "fodb_results_summary.md", _summary_markdown(evidence)) + _write_text(generated_dir / "table_fodb_workloads.tex", _workload_table(workload_descriptors)) + _write_text(generated_dir / "table_fodb_provenance.tex", _provenance_table(workload_descriptors)) + _write_text(generated_dir / "table_fodb_decisions.tex", _decision_table(decisions)) + _write_text( + generated_dir / "table_fodb_decoder_coverage.tex", + _decoder_coverage_table(compatibility_audit), + ) + _write_text( + generated_dir / "table_fodb_worker_transfer.tex", + _worker_transfer_table(worker_transfer_rows), + ) + _write_text(generated_dir / "table_fodb_coverage.tex", _coverage_table(evidence)) + _write_text(generated_dir / "table_fodb_versions.tex", _versions_table(records)) + _plot_workloads(package, manifests, support_item_ids, figures_dir / "fig_fodb_workload_distributions.pdf") + _plot_worker_scaling(aggregates, figures_dir / "fig_fodb_worker_scaling.pdf") + _plot_protocol_regret(decisions, figures_dir / "fig_fodb_protocol_regret.pdf") + return evidence + + +def _validate_and_measure(records: tuple[RunBundleRecord, ...], package: dict[str, Any]) -> tuple[Measurement, ...]: + base_total = len(BASE_PLAN_WORKLOADS) * len(EXPECTED_MACHINES) * 75 * len(EXPECTED_REPETITIONS) + worker12_total = sum(len(decoders) * len(EXPECTED_REPETITIONS) for decoders in WORKER12_DECODERS.values()) + worker16_total = sum(len(decoders) * len(EXPECTED_REPETITIONS) for decoders in WORKER16_DECODERS.values()) + expected_total = base_total + worker12_total + worker16_total + if len(records) != expected_total: + raise PaperAssetError(f"expected {expected_total} evidence bundles, found {len(records)}") + + decode_records = tuple(record for record in records if record.config.get("protocol_id") == "decode-memory") + loader_records = tuple(record for record in records if record.config.get("protocol_id") == "loader-supply") + assert_claim_scope(decode_records, ClaimScope.DECODER_CAPACITY) + assert_claim_scope(loader_records, ClaimScope.LOADER_SUPPLY) + + package_workloads = _required_object(package, "workloads") + manifest_ids = { + workload: _required_string(_required_object(package_workloads, workload), "manifest_id") + for workload in EXPECTED_ITEM_COUNTS + } + measurements: list[Measurement] = [] + cell_counts: Counter[tuple[str, str, str]] = Counter() + configuration_repetitions: dict[tuple[str, str, str], set[int]] = defaultdict(set) + worker12_decoders: dict[tuple[str, str], set[str]] = defaultdict(set) + worker16_decoders: dict[tuple[str, str], set[str]] = defaultdict(set) + for record in records: + measurement = _measurement(record, manifest_ids) + measurements.append(measurement) + cell_counts[(measurement.workload, measurement.machine_type, measurement.protocol)] += 1 + if measurement.protocol == "loader-supply" and measurement.workers == 12: + worker12_decoders[(measurement.workload, measurement.machine_type)].add(measurement.decoder) + if measurement.protocol == "loader-supply" and measurement.workers == 16: + worker16_decoders[(measurement.workload, measurement.machine_type)].add(measurement.decoder) + config_id = _required_string(record.config, "config_id") + configuration_repetitions[(measurement.workload, measurement.machine_type, config_id)].add( + measurement.repetition, + ) + + _validate_matrix_counts(cell_counts, configuration_repetitions) + for cell, expected_decoders in WORKER12_DECODERS.items(): + if worker12_decoders[cell] != set(expected_decoders): + raise PaperAssetError(f"workers=12 decoder selection does not match the frozen follow-up for {cell}") + for cell, expected_decoders in WORKER16_DECODERS.items(): + if worker16_decoders[cell] != set(expected_decoders): + raise PaperAssetError(f"workers=16 decoder selection does not match the frozen follow-up for {cell}") + return tuple(measurements) + + +def _measurement(record: RunBundleRecord, manifest_ids: dict[str, str]) -> Measurement: + config = record.config + plan_id = _required_string(config, "plan_id") + workload = PLAN_WORKLOADS[plan_id] + machine_type = _required_string(_required_object(record.platform, "identity"), "machine_type") + if machine_type not in EXPECTED_MACHINES: + raise PaperAssetError(f"unexpected machine type {machine_type!r}") + if plan_id in FOLLOWUP_PLAN_SCOPES: + eligible_decoders = FOLLOWUP_PLAN_SCOPES[plan_id].get((workload, machine_type)) + followup_workers = FOLLOWUP_PLAN_WORKERS[plan_id] + decoder = _required_string(config, "decoder_id") + requested_threads = _optional_int(config, "requested_threads") + followup_checks = ( + (eligible_decoders is not None, "follow-up plan/platform mismatch"), + (config.get("protocol_id") == "loader-supply", "follow-up protocol is not loader-supply"), + (config.get("num_workers") == followup_workers, "follow-up worker count is unexpected"), + (eligible_decoders is not None and decoder in eligible_decoders, "decoder was not eligible for follow-up"), + (requested_threads == CONTROLLED_THREADS.get(decoder), "follow-up thread profile is not controlled"), + ) + for valid, message in followup_checks: + if not valid: + raise PaperAssetError(f"{message} in {record.run_key}") + checks = ( + (config.get("runner_revision") == RUNNER_REVISION, "unexpected runner revision"), + (config.get("output_contract") == "normalized-rgb", "unexpected output contract"), + (record.dataset.get("workload_id") == workload, "plan/workload mismatch"), + (record.dataset.get("manifest_id") == manifest_ids[workload], "manifest mismatch"), + (not record.failures, "timed failures are present"), + (record.summary.get("status") == "complete", "incomplete summary"), + (len(record.samples) == 1, "expected one timed traversal"), + ) + for valid, message in checks: + if not valid: + raise PaperAssetError(f"{message} in {record.run_key}") + ordered_items = record.dataset.get("ordered_item_ids") + if not isinstance(ordered_items, list) or len(ordered_items) != EXPECTED_TIMED_ITEM_COUNTS[workload]: + raise PaperAssetError(f"unexpected timed support population for {workload} in {record.run_key}") + sample = record.samples[0] + return Measurement( + workload=workload, + machine_type=machine_type, + protocol=_required_string(config, "protocol_id"), + decoder=_required_string(config, "decoder_id"), + requested_threads=_optional_int(config, "requested_threads"), + workers=_optional_int(config, "num_workers"), + repetition=_required_int(config, "repetition"), + images_per_second=sample.items_processed / sample.elapsed_seconds, + run_key=record.run_key, + bundle_id=record.bundle_id, + ) + + +def _validate_matrix_counts( + cell_counts: Counter[tuple[str, str, str]], + configuration_repetitions: dict[tuple[str, str, str], set[int]], +) -> None: + for workload in EXPECTED_ITEM_COUNTS: + for machine_type in EXPECTED_MACHINES: + if cell_counts[(workload, machine_type, "decode-memory")] != 75: + raise PaperAssetError(f"incomplete decode-memory matrix for {workload}/{machine_type}") + worker12_count = len(WORKER12_DECODERS[(workload, machine_type)]) * len(EXPECTED_REPETITIONS) + worker16_count = len(WORKER16_DECODERS[(workload, machine_type)]) * len(EXPECTED_REPETITIONS) + followup_count = worker12_count + worker16_count + if cell_counts[(workload, machine_type, "loader-supply")] != 300 + followup_count: + raise PaperAssetError(f"incomplete loader-supply matrix for {workload}/{machine_type}") + expected_repetitions = set(EXPECTED_REPETITIONS) + if any(repetitions != expected_repetitions for repetitions in configuration_repetitions.values()): + raise PaperAssetError("at least one configuration lacks the five repetition blocks") + + +def _aggregate(measurements: tuple[Measurement, ...]) -> tuple[Aggregate, ...]: + grouped: dict[tuple[object, ...], list[Measurement]] = defaultdict(list) + for measurement in measurements: + key = ( + measurement.workload, + measurement.machine_type, + measurement.protocol, + measurement.decoder, + measurement.requested_threads, + measurement.workers, + ) + grouped[key].append(measurement) + aggregates: list[Aggregate] = [] + for aggregate_key, rows in grouped.items(): + ordered = sorted(rows, key=lambda row: row.repetition) + repetitions = tuple(row.repetition for row in ordered) + if repetitions != EXPECTED_REPETITIONS: + raise PaperAssetError(f"aggregate has repetition blocks {repetitions}, expected {EXPECTED_REPETITIONS}") + raw = tuple(row.images_per_second for row in ordered) + workload, machine_type, protocol, decoder, requested_threads, workers = aggregate_key + aggregates.append( + Aggregate( + workload=str(workload), + machine_type=str(machine_type), + protocol=str(protocol), + decoder=str(decoder), + requested_threads=requested_threads if isinstance(requested_threads, int) else None, + workers=workers if isinstance(workers, int) else None, + repetitions=repetitions, + raw_run_means=raw, + mean=statistics.fmean(raw), + sample_std=statistics.stdev(raw), + ), + ) + return tuple(sorted(aggregates, key=_aggregate_sort_key)) + + +def _aggregate_sort_key(row: Aggregate) -> tuple[str, ...]: + return ( + row.workload, + row.machine_type, + row.protocol, + row.decoder, + str(row.requested_threads), + str(row.workers), + ) + + +def _is_controlled(row: Aggregate) -> bool: + return row.requested_threads == CONTROLLED_THREADS.get(row.decoder) + + +def _decision_rows(aggregates: tuple[Aggregate, ...]) -> list[dict[str, Any]]: + rows: list[dict[str, Any]] = [] + for workload in EXPECTED_ITEM_COUNTS: + for machine_type in EXPECTED_MACHINES: + block = tuple( + row + for row in aggregates + if row.workload == workload and row.machine_type == machine_type and _is_controlled(row) + ) + decode = tuple(row for row in block if row.protocol == "decode-memory") + loader = tuple(row for row in block if row.protocol == "loader-supply") + expected_loader_count = ( + 48 + len(WORKER12_DECODERS[(workload, machine_type)]) + len(WORKER16_DECODERS[(workload, machine_type)]) + ) + if len(decode) != 12 or len(loader) != expected_loader_count: + raise PaperAssetError(f"controlled-thread block is incomplete for {workload}/{machine_type}") + decode_leader = _best(decode) + peak_by_decoder = { + decoder: _best(tuple(row for row in loader if row.decoder == decoder)) for decoder in _decoders(decode) + } + loader_leader = _best(tuple(peak_by_decoder.values())) + selected_loader = peak_by_decoder[decode_leader.decoder] + aggregate_regret = 1 - selected_loader.mean / loader_leader.mean + paired_regret = tuple( + 1 - selected / leader + for selected, leader in zip(selected_loader.raw_run_means, loader_leader.raw_run_means, strict=True) + ) + decode_ranks = _ranks({row.decoder: row.mean for row in decode}) + loader_ranks = _ranks({decoder: row.mean for decoder, row in peak_by_decoder.items()}) + rank_correlation = statistics.correlation( + [decode_ranks[decoder] for decoder in sorted(decode_ranks)], + [loader_ranks[decoder] for decoder in sorted(loader_ranks)], + ) + threshold = loader_leader.mean * (1 - PRACTICAL_MARGIN) + tier = [ + { + "decoder": row.decoder, + "mean_images_per_second": row.mean, + "workers": row.workers, + } + for row in sorted(peak_by_decoder.values(), key=lambda item: (-item.mean, item.configuration_label)) + if row.mean >= threshold + ] + rows.append( + { + "aggregate_regret_percent": aggregate_regret * 100, + "decode_leader": decode_leader.decoder, + "decode_leader_images_per_second": decode_leader.mean, + "loader_leader": loader_leader.decoder, + "loader_leader_images_per_second": loader_leader.mean, + "loader_leader_workers": loader_leader.workers, + "machine_type": machine_type, + "paired_regret_percent": [value * 100 for value in paired_regret], + "platform": PLATFORM_LABELS[machine_type], + "rank_correlation": rank_correlation, + "selected_decoder_loader_workers": selected_loader.workers, + "strict_leader_match": decode_leader.decoder == loader_leader.decoder, + "top_tier": tier, + "workload": workload, + }, + ) + return rows + + +def _compatibility_audit() -> dict[str, Any]: + return { + "item_count": COMPATIBILITY_AUDIT_ITEM_COUNT, + "linux_receipts": [ + { + "job": "imread-20260802-180331-9cbbc8f2", + "plan_id": "2cab69bcdb92b33c41f8d0ec225ccb91e3e314f792caca517e7280a667578250", + "result": ( + "ajpegli support audit completed with 0/277 before the empty support set stopped the campaign" + ), + }, + { + "committed_bundles": 11, + "job": "imread-20260802-181101-9cbbc8f2", + "plan_id": "98e8f88e2820b1a0992d0a72cb299d95d52fb2d0d700884567eac54d23282e2e", + "result": "completed", + }, + ], + "manifest_id": COMPATIBILITY_AUDIT_MANIFEST_ID, + "output_contract": "normalized-rgb", + "package_id": COMPATIBILITY_AUDIT_PACKAGE_ID, + "platform": { + "machine_type": "c3-standard-4", + "operating_system": "Ubuntu 24.04", + "zone": "us-central1-a", + }, + "process_context": "main-process support audit", + "runner_revision": "9cbbc8f212ad9a384fbabf1dde582023077e83dcda0ac285baf0c16351febb8f", + "selection": "276 FODB empty-DHT exclusions plus one four-component RGB-contract sentinel", + "successes": dict(sorted(COMPATIBILITY_AUDIT_SUCCESSES.items())), + "categories": { + "empty_dht_bitstream": { + "description": ( + "Progressive FODB WhatsApp JPEGs containing empty DHT markers; success measures decoder " + "recovery from this malformed bitstream pattern." + ), + "item_count": COMPATIBILITY_AUDIT_EMPTY_DHT_ITEM_COUNT, + "successes": dict(sorted(COMPATIBILITY_AUDIT_EMPTY_DHT_SUCCESSES.items())), + }, + "four_component_rgb": { + "description": ( + "A four-component JPEG; success requires conversion to the normalized three-channel RGB contract." + ), + "filename": "ILSVRC2012_val_00019877.JPEG", + "item_count": COMPATIBILITY_AUDIT_FOUR_COMPONENT_ITEM_COUNT, + "sha256": "75413aece0dc58bcd9d4b89f664ab04cee3ade28317d81aeace455029e0000ba", + "successes": dict(sorted(COMPATIBILITY_AUDIT_FOUR_COMPONENT_SUCCESSES.items())), + }, + }, + } + + +def _thread_rows(aggregates: tuple[Aggregate, ...]) -> list[dict[str, Any]]: + rows: list[dict[str, Any]] = [] + for workload in EXPECTED_ITEM_COUNTS: + for machine_type in EXPECTED_MACHINES: + for decoder in sorted(CONTROLLED_THREADS): + decode_default = _only(aggregates, (workload, machine_type, "decode-memory", decoder, None, None)) + decode_one = _only(aggregates, (workload, machine_type, "decode-memory", decoder, 1, None)) + loader_default = _best( + tuple( + row + for row in aggregates + if row.workload == workload + and row.machine_type == machine_type + and row.protocol == "loader-supply" + and row.decoder == decoder + and row.requested_threads is None + and row.workers in BASE_WORKERS + ), + ) + loader_one = _best( + tuple( + row + for row in aggregates + if row.workload == workload + and row.machine_type == machine_type + and row.protocol == "loader-supply" + and row.decoder == decoder + and row.requested_threads == 1 + and row.workers in BASE_WORKERS + ), + ) + rows.append( + { + "decode_default_vs_one_percent": (decode_default.mean / decode_one.mean - 1) * 100, + "decoder": decoder, + "loader_default_vs_one_percent": (loader_default.mean / loader_one.mean - 1) * 100, + "machine_type": machine_type, + "platform": PLATFORM_LABELS[machine_type], + "workload": workload, + }, + ) + return rows + + +def _worker_transfer_rows(aggregates: tuple[Aggregate, ...]) -> list[dict[str, Any]]: + rows: list[dict[str, Any]] = [] + for machine_type in EXPECTED_MACHINES: + peaks: dict[str, dict[str, Aggregate]] = {} + for workload in EXPECTED_ITEM_COUNTS: + peaks[workload] = { + decoder: _best( + tuple( + row + for row in aggregates + if row.workload == workload + and row.machine_type == machine_type + and row.protocol == "loader-supply" + and row.decoder == decoder + and _is_controlled(row) + ), + ) + for decoder in sorted({row.decoder for row in aggregates if _is_controlled(row)}) + } + changed = [ + decoder + for decoder in sorted(peaks["fodb-native"]) + if peaks["fodb-native"][decoder].workers != peaks["fodb-mixed"][decoder].workers + ] + rows.append( + { + "changed_decoders": changed, + "machine_type": machine_type, + "mixed_peak_worker_counts": dict( + sorted(Counter(row.workers for row in peaks["fodb-mixed"].values()).items()), + ), + "native_peak_worker_counts": dict( + sorted(Counter(row.workers for row in peaks["fodb-native"].values()).items()), + ), + "platform": PLATFORM_LABELS[machine_type], + }, + ) + return rows + + +def _worker16_candidate_rows( + aggregates: tuple[Aggregate, ...], + worker12_decoders: dict[tuple[str, str], tuple[str, ...]] | None = None, +) -> dict[str, Any]: + selection = WORKER12_DECODERS if worker12_decoders is None else worker12_decoders + cells: list[dict[str, Any]] = [] + for (workload, machine_type), decoders in sorted(selection.items()): + selected: list[dict[str, Any]] = [] + for decoder in decoders: + curve = tuple( + row + for row in aggregates + if row.workload == workload + and row.machine_type == machine_type + and row.protocol == "loader-supply" + and row.decoder == decoder + and row.requested_threads == CONTROLLED_THREADS.get(decoder) + and row.workers in WORKER12_DECISION_GRID + ) + expected_workers = set(WORKER12_DECISION_GRID) + if len(curve) != len(expected_workers) or {row.workers for row in curve} != expected_workers: + raise PaperAssetError( + f"workers=12 follow-up curve is incomplete for {workload}/{machine_type}/{decoder}", + ) + worker12 = _only( + aggregates, + (workload, machine_type, "loader-supply", decoder, CONTROLLED_THREADS.get(decoder), 12), + ) + worker8 = _only( + aggregates, + (workload, machine_type, "loader-supply", decoder, CONTROLLED_THREADS.get(decoder), 8), + ) + paired_gain_ratios = tuple( + worker12_value / worker8_value + for worker12_value, worker8_value in zip( + worker12.raw_run_means, + worker8.raw_run_means, + strict=True, + ) + ) + gain_from_workers_8 = worker12.mean / worker8.mean - 1 + if gain_from_workers_8 >= WORKER16_MINIMUM_GAIN and min(paired_gain_ratios) >= 1: + selected.append( + { + "decoder": decoder, + "gain_from_workers_8_percent": gain_from_workers_8 * 100, + "minimum_paired_gain_ratio": min(paired_gain_ratios), + "workers_12_images_per_second": worker12.mean, + }, + ) + cells.append( + { + "decoders": selected, + "machine_type": machine_type, + "platform": PLATFORM_LABELS[machine_type], + "workload": workload, + }, + ) + candidate_count = sum(len(cell["decoders"]) for cell in cells) + return { + "bundle_count_if_launched": candidate_count * len(EXPECTED_REPETITIONS), + "candidate_count": candidate_count, + "cells": cells, + "criterion": ( + "workers=12 is at least 5% faster than workers=8 by the five-block mean " + "and every paired workers=12/workers=8 block has a ratio of at least 1" + ), + "minimum_mean_gain_percent": WORKER16_MINIMUM_GAIN * 100, + "repetitions_if_launched": len(EXPECTED_REPETITIONS), + } + + +def _evidence_document( + records: tuple[RunBundleRecord, ...], + measurements: tuple[Measurement, ...], + aggregates: tuple[Aggregate, ...], + sections: dict[str, Any], +) -> dict[str, Any]: + run_keys = sorted(measurement.run_key for measurement in measurements) + bundle_ids = sorted(measurement.bundle_id for measurement in measurements) + return { + "aggregates": [_aggregate_dict(row) for row in aggregates], + "coverage": { + "bundles_by_workload": dict( + sorted( + Counter(PLAN_WORKLOADS[_required_string(record.config, "plan_id")] for record in records).items(), + ), + ), + "committed_evidence_bundles": len(records), + "expected_repetitions_per_configuration": len(EXPECTED_REPETITIONS), + "manifest_items": EXPECTED_ITEM_COUNTS, + "timed_common_support_items": EXPECTED_TIMED_ITEM_COUNTS, + "timed_failure_bundles": sum(bool(record.failures) for record in records), + }, + "decisions": sections["decisions"], + "matrix": { + "machine_types": list(EXPECTED_MACHINES), + "plan_ids": { + workload: sorted( + plan_id for plan_id, plan_workload in PLAN_WORKLOADS.items() if plan_workload == workload + ) + for workload in EXPECTED_ITEM_COUNTS + }, + "practical_margin_percent": PRACTICAL_MARGIN * 100, + "runner_revision": RUNNER_REVISION, + "broad_worker_grid": list(BASE_WORKERS), + "worker_grid": list(WORKER_GRID), + "workers_12_decoders": { + f"{workload}/{machine_type}": list(decoders) + for (workload, machine_type), decoders in sorted(WORKER12_DECODERS.items()) + }, + "workers_16_decoders": { + f"{workload}/{machine_type}": list(decoders) + for (workload, machine_type), decoders in sorted(WORKER16_DECODERS.items()) + if decoders + }, + }, + "compatibility_audit": sections["compatibility_audit"], + "provenance": { + "bundle_ids_sha256": _sequence_digest(bundle_ids), + "generator_sha256": hashlib.sha256(Path(__file__).read_bytes()).hexdigest(), + "run_keys": run_keys, + "run_keys_sha256": _sequence_digest(run_keys), + }, + "schema_version": "1.7", + "thread_controls": sections["thread_controls"], + "worker_transfer": sections["worker_transfer"], + "worker16_candidates": sections["worker16_candidates"], + "workloads": sections["workloads"], + } + + +def _aggregate_dict(row: Aggregate) -> dict[str, Any]: + return { + "decoder": row.decoder, + "machine_type": row.machine_type, + "mean_images_per_second": row.mean, + "protocol": row.protocol, + "raw_run_means": list(row.raw_run_means), + "repetitions": list(row.repetitions), + "requested_threads": row.requested_threads, + "sample_std_images_per_second": row.sample_std, + "workers": row.workers, + "workload": row.workload, + } + + +def _load_package_manifests(package_path: Path, package: dict[str, Any]) -> dict[str, list[dict[str, Any]]]: + archive = _required_object(package, "archive") + archive_path = package_path.parent / _required_string(archive, "file") + manifests: dict[str, list[dict[str, Any]]] = {} + try: + with tarfile.open(archive_path) as tar: + for workload in EXPECTED_ITEM_COUNTS: + source = tar.extractfile(f"manifests/{workload}.json") + if source is None: + raise PaperAssetError(f"package archive has no manifest for {workload}") + document = json.load(source) + if not isinstance(document, dict): + raise PaperAssetError(f"manifest for {workload} must be an object") + raw_items = _required_list(document, "items") + if not all(isinstance(item, dict) for item in raw_items): + raise PaperAssetError(f"manifest for {workload} contains a non-object item") + manifests[workload] = raw_items + except (OSError, tarfile.TarError, json.JSONDecodeError) as exc: + raise PaperAssetError(f"cannot read package manifests from {archive_path}: {exc}") from exc + return manifests + + +def _support_item_ids(records: tuple[RunBundleRecord, ...]) -> dict[str, set[str]]: + populations: dict[str, set[str]] = {} + for record in records: + workload = _required_string(record.dataset, "workload_id") + raw_items = record.dataset.get("ordered_item_ids") + if not isinstance(raw_items, list) or not all(isinstance(item, str) for item in raw_items): + raise PaperAssetError(f"bundle {record.run_key} has invalid support item IDs") + item_ids = set(raw_items) + observed = populations.setdefault(workload, item_ids) + if observed != item_ids: + raise PaperAssetError(f"timed support population differs across {workload} bundles") + if set(populations) != set(EXPECTED_ITEM_COUNTS): + raise PaperAssetError("timed support populations do not cover both FODB workloads") + return populations + + +def _workload_descriptors( + package: dict[str, Any], + manifests: dict[str, list[dict[str, Any]]], + support_item_ids: dict[str, set[str]], +) -> list[dict[str, Any]]: + package_items = _required_list(_required_object(package, "provenance"), "items") + by_sha = {_required_string(item, "jpeg_sha256"): item for item in package_items if isinstance(item, dict)} + rows: list[dict[str, Any]] = [] + for workload in EXPECTED_ITEM_COUNTS: + manifest = manifests[workload] + timed_manifest = [item for item in manifest if _required_string(item, "item_id") in support_item_ids[workload]] + excluded_manifest = [ + item for item in manifest if _required_string(item, "item_id") not in support_item_ids[workload] + ] + selected = [by_sha[_required_string(item, "sha256")] for item in timed_manifest] + excluded = [by_sha[_required_string(item, "sha256")] for item in excluded_manifest] + megapixels = [_jpeg_number(item, "megapixels") for item in selected] + compressed_bytes = [_required_int(item, "jpeg_bytes") for item in selected] + bits_per_pixel = [_required_number(item, "bits_per_pixel") for item in selected] + quality = [_jpeg_number(item, "quality_estimate") for item in selected] + progressive = sum(bool(_required_object(item, "jpeg").get("progressive")) for item in selected) + provenance = Counter(_required_string(item, "provenance") for item in selected) + subsampling = Counter(_required_string(_required_object(item, "jpeg"), "subsampling") for item in selected) + rows.append( + { + "bits_per_pixel": _distribution(bits_per_pixel), + "compressed_bytes": _distribution(compressed_bytes), + "estimated_quality": _distribution(quality), + "excluded_items": len(excluded), + "excluded_profile": _excluded_profile(excluded), + "items": len(selected), + "manifest_items": len(manifest), + "megapixels": _distribution(megapixels), + "progressive_items": progressive, + "provenance": dict(sorted(provenance.items())), + "subsampling": dict(sorted(subsampling.items())), + "total_compressed_bytes": sum(compressed_bytes), + "workload": workload, + }, + ) + return rows + + +def _excluded_profile(items: list[dict[str, Any]]) -> dict[str, Any]: + if not items: + return {"estimated_quality": {}, "progressive_items": 0, "provenance": {}} + return { + "estimated_quality": dict(sorted(Counter(_jpeg_number(item, "quality_estimate") for item in items).items())), + "progressive_items": sum(bool(_required_object(item, "jpeg").get("progressive")) for item in items), + "provenance": dict(sorted(Counter(_required_string(item, "provenance") for item in items).items())), + } + + +def _distribution(values: list[float] | list[int]) -> dict[str, float | int]: + ordered = sorted(float(value) for value in values) + return { + "maximum": ordered[-1], + "median": statistics.median(ordered), + "minimum": ordered[0], + "q10": _linear_quantile(ordered, 0.10), + "q90": _linear_quantile(ordered, 0.90), + } + + +def _linear_quantile(ordered: list[float], probability: float) -> float: + position = (len(ordered) - 1) * probability + lower = int(position) + upper = min(lower + 1, len(ordered) - 1) + fraction = position - lower + return ordered[lower] * (1 - fraction) + ordered[upper] * fraction + + +def _best(rows: tuple[Aggregate, ...]) -> Aggregate: + if not rows: + raise PaperAssetError("cannot select a best configuration from an empty set") + return min(rows, key=lambda row: (-row.mean, row.configuration_label, row.workers or -1)) + + +def _decoders(rows: tuple[Aggregate, ...]) -> tuple[str, ...]: + return tuple(sorted({row.decoder for row in rows})) + + +def _ranks(values: dict[str, float]) -> dict[str, float]: + ordered = sorted(values.items(), key=lambda item: (-item[1], item[0])) + result: dict[str, float] = {} + start = 0 + while start < len(ordered): + end = start + 1 + while end < len(ordered) and ordered[end][1] == ordered[start][1]: + end += 1 + rank = (start + 1 + end) / 2 + for key, _ in ordered[start:end]: + result[key] = rank + start = end + return result + + +AggregateKey = tuple[str, str, str, str, int | None, int | None] + + +def _only(rows: tuple[Aggregate, ...], key: AggregateKey) -> Aggregate: + selected = tuple( + row + for row in rows + if ( + row.workload, + row.machine_type, + row.protocol, + row.decoder, + row.requested_threads, + row.workers, + ) + == key + ) + if len(selected) != 1: + raise PaperAssetError(f"expected one aggregate, found {len(selected)}") + return selected[0] + + +def _summary_markdown(evidence: dict[str, Any]) -> str: + compatibility_audit = evidence["compatibility_audit"] + empty_dht = compatibility_audit["categories"]["empty_dht_bitstream"] + four_component = compatibility_audit["categories"]["four_component_rgb"] + lines = [ + "# Generated FODB evidence", + "", + "All values below are generated from the exact evidence plan IDs in `fodb_evidence.json`.", + "", + "## Workloads", + "", + "| Workload | Timed / manifest | Median MP | Median compressed MiB | Median estimated quality | Progressive |", + "| --- | ---: | ---: | ---: | ---: | ---: |", + ] + lines.extend( + ( + f"| {row['workload']} | {row['items']} / {row['manifest_items']} | " + f"{row['megapixels']['median']:.3f} | " + f"{row['compressed_bytes']['median'] / 2**20:.3f} | {row['estimated_quality']['median']:.0f} | " + f"{row['progressive_items']} |" + ) + for row in evidence["workloads"] + ) + lines.extend( + [ + "", + "## Compatibility audit", + "", + "The empty-DHT test measures recovery from one malformed bitstream pattern. " + "The four-component test measures conversion to the normalized three-channel RGB contract; " + "the sentinel is not classified as corrupt.", + "", + "| Decoder | Empty-DHT bitstreams | Four-component RGB | Combined |", + "| --- | ---: | ---: | ---: |", + ], + ) + lines.extend( + ( + f"| `{decoder}` | {empty_dht['successes'][decoder]}/{empty_dht['item_count']} | " + f"{four_component['successes'][decoder]}/{four_component['item_count']} | " + f"{compatibility_audit['successes'][decoder]}/{compatibility_audit['item_count']} |" + ) + for decoder in sorted(compatibility_audit["successes"]) + ) + lines.extend( + [ + "", + "## Protocol decision", + "", + "| Workload | Platform | Decode leader | Loader leader | Workers | Regret | " + "Spearman rho | Top 10% throughput group |", + "| --- | --- | --- | --- | ---: | ---: | ---: | --- |", + ], + ) + for row in evidence["decisions"]: + tier = ", ".join(item["decoder"] for item in row["top_tier"]) + lines.append( + f"| {row['workload']} | {row['platform']} | {row['decode_leader']} | {row['loader_leader']} | " + f"{row['loader_leader_workers']} | {row['aggregate_regret_percent']:.2f}% | " + f"{row['rank_correlation']:.2f} | {tier} |", + ) + lines.extend( + [ + "", + "## Peak-worker transfer", + "", + "| Platform | Native peak-worker counts | Mixed peak-worker counts | Changed decoders |", + "| --- | --- | --- | --- |", + ], + ) + for row in evidence["worker_transfer"]: + native = ", ".join(f"w={key}: {value}" for key, value in row["native_peak_worker_counts"].items()) + mixed = ", ".join(f"w={key}: {value}" for key, value in row["mixed_peak_worker_counts"].items()) + lines.append(f"| {row['platform']} | {native} | {mixed} | {', '.join(row['changed_decoders']) or 'none'} |") + return "\n".join(lines) + "\n" + + +def _workload_table(rows: list[dict[str, Any]]) -> str: + body = [] + for row in rows: + line = ( + f"{_latex_workload(row['workload'])} & {row['items']:,}/{row['manifest_items']:,} & " + f"{row['megapixels']['median']:.2f} [{row['megapixels']['q10']:.2f}, {row['megapixels']['q90']:.2f}] & " + f"{row['compressed_bytes']['median'] / 2**20:.2f} & {row['bits_per_pixel']['median']:.2f} & " + f"{row['estimated_quality']['median']:.0f} & {row['progressive_items']:,} " + ) + body.append(line) + return (r"\\" + "\n").join(body) + "\n" + + +def _decision_table(rows: list[dict[str, Any]]) -> str: + body = [] + for row in rows: + line = ( + f"{_latex_workload(row['workload'])} & {row['platform']} & \\texttt{{{row['decode_leader']}}} & " + f"\\texttt{{{row['loader_leader']}}} ({row['loader_leader_workers']} workers) & " + f"{row['aggregate_regret_percent']:.1f}\\% & {row['rank_correlation']:.2f} " + ) + body.append(line) + return (r"\\" + "\n").join(body) + "\n" + + +def _decoder_coverage_table(audit: dict[str, Any]) -> str: + categories = audit.get("categories") + if not isinstance(categories, dict): + raise PaperAssetError("decoder coverage table requires the recorded two-category compatibility audit") + empty_dht = categories["empty_dht_bitstream"] + four_component = categories["four_component_rgb"] + empty_dht_successes = empty_dht["successes"] + four_component_successes = four_component["successes"] + combined_successes = audit["successes"] + body = [] + for decoder in sorted(combined_successes): + bitstream_result = f"{empty_dht_successes[decoder]}/{empty_dht['item_count']}" + four_component_result = f"{four_component_successes[decoder]}/{four_component['item_count']}" + combined_result = f"{combined_successes[decoder]}/{audit['item_count']}" + body.append( + f"\\texttt{{{decoder}}} & {bitstream_result} & {four_component_result} & {combined_result} ", + ) + return (r"\\" + "\n").join(body) + "\n" + + +def _provenance_table(rows: list[dict[str, Any]]) -> str: + body = [] + for row in rows: + counts = row["provenance"] + body.append( + f"{_latex_workload(row['workload'])} & {counts.get('orig', 0):,} & " + f"{counts.get('facebook', 0):,} & {counts.get('instagram', 0):,} & " + f"{counts.get('telegram', 0):,} & {counts.get('twitter', 0):,} & " + f"{counts.get('whatsapp', 0):,} ", + ) + return (r"\\" + "\n").join(body) + "\n" + + +def _worker_transfer_table(rows: list[dict[str, Any]]) -> str: + body = [] + for row in rows: + native = row["native_peak_worker_counts"] + mixed = row["mixed_peak_worker_counts"] + native_counts = ", ".join(f"{workers}: {count}" for workers, count in sorted(native.items())) + mixed_counts = ", ".join(f"{workers}: {count}" for workers, count in sorted(mixed.items())) + body.append( + f"{row['platform']} & {native_counts} & {mixed_counts} & {len(row['changed_decoders'])}/12 ", + ) + return (r"\\" + "\n").join(body) + "\n" + + +def _coverage_table(evidence: dict[str, Any]) -> str: + coverage = evidence["coverage"] + native = ( + f"FODB-native & {coverage['timed_common_support_items']['fodb-native']:,} / " + f"{coverage['manifest_items']['fodb-native']:,} " + f"& 0 & {coverage['bundles_by_workload']['fodb-native']:,} " + ) + mixed = ( + f"FODB-mixed & {coverage['timed_common_support_items']['fodb-mixed']:,} / " + f"{coverage['manifest_items']['fodb-mixed']:,} " + f"& 0 & {coverage['bundles_by_workload']['fodb-mixed']:,} " + ) + return native + r"\\" + "\n" + mixed + "\n" + + +def _versions_table(records: tuple[RunBundleRecord, ...]) -> str: + wanted = { + "ajpegli", + "imagecodecs", + "imageio", + "jpeg4py", + "kornia-rs", + "opencv-python-headless", + "pillow", + "pyvips", + "scikit-image", + "simplejpeg", + "torch", + "torchvision", + "pyturbojpeg", + } + versions: dict[str, set[str]] = defaultdict(set) + for record in records: + distributions = record.environment.get("distributions") + if not isinstance(distributions, list): + raise PaperAssetError("environment distributions are missing") + for distribution in distributions: + if isinstance(distribution, dict) and distribution.get("name") in wanted: + versions[str(distribution["name"])].add(str(distribution.get("version"))) + if set(versions) != wanted or any(len(values) != 1 for values in versions.values()): + raise PaperAssetError("decoder package versions differ across evidence environments") + return (r"\\" + "\n").join( + f"\\texttt{{{name}}} & \\texttt{{{next(iter(versions[name]))}}} " for name in sorted(versions) + ) + "\n" + + +def _plot_workloads( + package: dict[str, Any], + manifests: dict[str, list[dict[str, Any]]], + support_item_ids: dict[str, set[str]], + destination: Path, +) -> None: + plt = _pyplot() + package_items = _required_list(_required_object(package, "provenance"), "items") + by_sha = {_required_string(item, "jpeg_sha256"): item for item in package_items if isinstance(item, dict)} + data: dict[str, list[dict[str, Any]]] = {} + for workload, manifest in manifests.items(): + selected = [item for item in manifest if _required_string(item, "item_id") in support_item_ids[workload]] + data[workload.replace("fodb-", "FODB-")] = [by_sha[_required_string(item, "sha256")] for item in selected] + extractors = ( + ("Megapixels", lambda item: _jpeg_number(item, "megapixels"), True), + ("Compressed size (MiB)", lambda item: _required_int(item, "jpeg_bytes") / 2**20, True), + ("Bits per pixel", lambda item: _required_number(item, "bits_per_pixel"), False), + ("Estimated quality", lambda item: _jpeg_number(item, "quality_estimate"), False), + ) + figure, axes = plt.subplots(2, 2, figsize=(7.1, 5.0), constrained_layout=True) + for axis, (label, extractor, logarithmic) in zip(axes.flat, extractors, strict=True): + for workload, workload_items in data.items(): + values = sorted(extractor(item) for item in workload_items) + probabilities = [(index + 1) / len(values) for index in range(len(values))] + axis.step(values, probabilities, where="post", label=workload) + if logarithmic: + axis.set_xscale("log") + axis.set_xlabel(label) + axis.set_ylabel("ECDF") + axis.grid(alpha=0.2) + axes[0, 0].legend(frameon=False) + figure.savefig(destination) + plt.close(figure) + + +def _plot_worker_scaling(aggregates: tuple[Aggregate, ...], destination: Path) -> None: + plt = _pyplot() + colors = dict(zip(PRIMARY_DECODERS, ("#4C78A8", "#F58518", "#54A24B", "#B279A2"), strict=True)) + figure, axes = plt.subplots(4, 2, figsize=(7.1, 8.4), sharex=True, constrained_layout=True) + for row_index, machine_type in enumerate(EXPECTED_MACHINES): + for column_index, workload in enumerate(EXPECTED_ITEM_COUNTS): + axis = axes[row_index, column_index] + for decoder_index, decoder in enumerate(PRIMARY_DECODERS): + curve = sorted( + ( + point + for point in aggregates + if point.workload == workload + and point.machine_type == machine_type + and point.protocol == "loader-supply" + and point.decoder == decoder + and point.requested_threads == CONTROLLED_THREADS.get(decoder) + and point.workers is not None + ), + key=lambda point: point.workers if point.workers is not None else -1, + ) + expected_workers = set(BASE_WORKERS) + if decoder in WORKER12_DECODERS[(workload, machine_type)]: + expected_workers.add(12) + if decoder in WORKER16_DECODERS[(workload, machine_type)]: + expected_workers.add(16) + curve_workers = [point.workers for point in curve if point.workers is not None] + if set(curve_workers) != expected_workers or len(curve_workers) != len(expected_workers): + raise PaperAssetError(f"worker curve is incomplete for {workload}/{machine_type}/{decoder}") + paired_ratios = [ + tuple( + value / baseline + for value, baseline in zip(point.raw_run_means, curve[0].raw_run_means, strict=True) + ) + for point in curve + ] + means = [statistics.fmean(values) for values in paired_ratios] + axis.plot(curve_workers, means, marker="o", color=colors[decoder], label=decoder) + jitter = (decoder_index - 1.5) * 0.06 + for workers, values in zip(curve_workers, paired_ratios, strict=True): + axis.scatter( + [workers + jitter] * len(values), + values, + color=colors[decoder], + alpha=0.28, + s=9, + linewidths=0, + ) + axis.axhline(1, color="0.45", linewidth=0.8, linestyle="--") + axis.grid(alpha=0.18) + axis.set_title(f"{PLATFORM_LABELS[machine_type]} — {workload}", fontsize=9) + if row_index == len(EXPECTED_MACHINES) - 1: + axis.set_xlabel("DataLoader workers") + axis.set_xticks(WORKER_GRID) + figure.supylabel("Relative DataLoader throughput\nHigher is better", fontsize=9) + axes[0, 0].legend(frameon=False, ncol=2, fontsize=8) + figure.savefig(destination) + plt.close(figure) + + +def _plot_protocol_regret(decisions: list[dict[str, Any]], destination: Path) -> None: + plt = _pyplot() + labels = [f"{row['platform']}\n{row['workload'].removeprefix('fodb-')}" for row in decisions] + paired = [row["paired_regret_percent"] for row in decisions] + means = [statistics.fmean(values) for values in paired] + colors = ["#4C78A8" if row["strict_leader_match"] else "#E45756" for row in decisions] + figure, axis = plt.subplots(figsize=(7.1, 3.4), constrained_layout=True) + positions = list(range(len(decisions))) + axis.bar(positions, means, color=colors, alpha=0.82) + for position, values in zip(positions, paired, strict=True): + offsets = (-0.12, -0.06, 0, 0.06, 0.12) + axis.scatter([position + offset for offset in offsets], values, color="black", s=12, alpha=0.65, zorder=3) + axis.axhline(0, color="0.3", linewidth=0.8) + axis.axhline(PRACTICAL_MARGIN * 100, color="0.3", linewidth=0.9, linestyle="--", label="10% margin") + axis.set_xticks(positions, labels, rotation=25, ha="right") + axis.set_ylabel("DataLoader throughput loss (%)\nLower is better") + axis.grid(axis="y", alpha=0.2) + axis.legend(frameon=False) + figure.savefig(destination) + plt.close(figure) + + +def _pyplot() -> Any: + try: + import matplotlib as mpl + + mpl.use("Agg") + import matplotlib.pyplot as plt + except ImportError as exc: + raise PaperAssetError("paper figures require `uv run --extra plot`") from exc + return plt + + +def _latex_workload(workload: str) -> str: + return workload.replace("fodb-", "FODB-") + + +def _latex_decoder_list(value: str) -> str: + return ", ".join(f"\\texttt{{{item.strip()}}}" for item in value.split(",")) + + +def _sequence_digest(values: list[str]) -> str: + return hashlib.sha256(("\n".join(values) + "\n").encode()).hexdigest() + + +def _jpeg_number(item: object, key: str) -> float: + if not isinstance(item, dict): + raise PaperAssetError("package item must be an object") + return _required_number(_required_object(item, "jpeg"), key) + + +def _read_object(path: Path) -> dict[str, Any]: + try: + value = json.loads(path.read_text()) + except (OSError, json.JSONDecodeError) as exc: + raise PaperAssetError(f"cannot read {path}: {exc}") from exc + if not isinstance(value, dict): + raise PaperAssetError(f"{path} must contain a JSON object") + return value + + +def _required_object(payload: dict[str, Any], key: str) -> dict[str, Any]: + value = payload.get(key) + if not isinstance(value, dict): + raise PaperAssetError(f"field {key!r} must be an object") + return value + + +def _required_list(payload: dict[str, Any], key: str) -> list[Any]: + value = payload.get(key) + if not isinstance(value, list): + raise PaperAssetError(f"field {key!r} must be a list") + return value + + +def _required_string(payload: dict[str, Any], key: str) -> str: + value = payload.get(key) + if not isinstance(value, str) or not value: + raise PaperAssetError(f"field {key!r} must be a non-empty string") + return value + + +def _required_int(payload: dict[str, Any], key: str) -> int: + value = payload.get(key) + if isinstance(value, bool) or not isinstance(value, int): + raise PaperAssetError(f"field {key!r} must be an integer") + return value + + +def _optional_int(payload: dict[str, Any], key: str) -> int | None: + value = payload.get(key) + if value is None: + return None + if isinstance(value, bool) or not isinstance(value, int): + raise PaperAssetError(f"field {key!r} must be an integer or null") + return value + + +def _required_number(payload: dict[str, Any], key: str) -> float: + value = payload.get(key) + if isinstance(value, bool) or not isinstance(value, (int, float)): + raise PaperAssetError(f"field {key!r} must be numeric") + return float(value) + + +def _write_json(path: Path, payload: object) -> None: + _write_text(path, json.dumps(payload, ensure_ascii=False, indent=2, sort_keys=True) + "\n") + + +def _write_text(path: Path, content: str) -> None: + path.write_text(content) + + +def main() -> None: + parser = argparse.ArgumentParser(description="Generate claim-scoped FODB paper evidence and vector figures.") + parser.add_argument("--artifacts", type=Path, required=True, help="Hydrated schema-2 artifact root.") + parser.add_argument("--package", type=Path, required=True, help="FODB package.json used by the campaigns.") + parser.add_argument( + "--output", + type=Path, + required=True, + help="Paper directory containing generated/ and figures/.", + ) + args = parser.parse_args() + evidence = build_paper_assets(artifact_root=args.artifacts, package_path=args.package, output_root=args.output) + print(json.dumps({"bundles": evidence["coverage"]["committed_evidence_bundles"], "status": "generated"})) + + +if __name__ == "__main__": + main() diff --git a/imread_benchmark/analysis/publication.py b/imread_benchmark/analysis/publication.py new file mode 100644 index 0000000..14a9277 --- /dev/null +++ b/imread_benchmark/analysis/publication.py @@ -0,0 +1,268 @@ +from __future__ import annotations + +import hashlib +import json +import re +import statistics +import tempfile +from dataclasses import dataclass +from pathlib import Path +from typing import Any + +import yaml + +from imread_benchmark.analysis.canonical import RunBundleRecord, load_bundles +from imread_benchmark.analysis.claims import ClaimScope, assert_claim_scope +from imread_benchmark.platforms import platform_comparison_id, platform_location + +PUBLICATION_SCHEMA_VERSION = "2.0" +_REVISION = re.compile(r"^[0-9a-f]{40}(?:[0-9a-f]{24})?$") +_OUTPUT_FILES = ("provenance.json", "results.json") + + +class PublicationError(RuntimeError): + pass + + +@dataclass(frozen=True, slots=True) +class PublicationSpec: + claim_scope: ClaimScope + filters: dict[str, object] + statistic: str + practical_margin_percent: float + generator_revision: str + + +def publish( + *, + artifact_root: str | Path, + spec_path: str | Path, + output_dir: str | Path, + check: bool = False, +) -> None: + spec = _load_spec(spec_path) + records = _filter_records(load_bundles(artifact_root), spec.filters) + assert_claim_scope(records, spec.claim_scope) + generated = _render(records, spec) + destination = Path(output_dir) + if check: + _check_outputs(destination, generated) + return + destination.mkdir(parents=True, exist_ok=True) + for name, content in generated.items(): + _atomic_write(destination / name, content) + + +def _load_spec(path: str | Path) -> PublicationSpec: + source = Path(path) + try: + document = yaml.safe_load(source.read_text()) + except (OSError, yaml.YAMLError) as exc: + raise PublicationError(f"cannot read publication spec {source}: {exc}") from exc + if not isinstance(document, dict): + raise PublicationError("publication spec must be a YAML object") + if document.get("schema_version") != PUBLICATION_SCHEMA_VERSION: + raise PublicationError(f"unsupported publication schema: {document.get('schema_version')!r}") + try: + claim_scope = ClaimScope(_required_string(document, "claim_scope")) + except ValueError as exc: + raise PublicationError(str(exc)) from exc + filters = document.get("filters") + if not isinstance(filters, dict) or not all(isinstance(key, str) for key in filters): + raise PublicationError("publication filters must be an object with string keys") + statistic = _required_string(document, "statistic") + if statistic not in {"images_per_second", "elapsed_seconds", "microseconds_per_image"}: + raise PublicationError(f"unsupported publication statistic: {statistic!r}") + margin = document.get("practical_margin_percent") + if isinstance(margin, bool) or not isinstance(margin, (int, float)) or margin < 0: + raise PublicationError("practical_margin_percent must be non-negative") + revision = _required_string(document, "generator_revision") + if _REVISION.fullmatch(revision) is None: + raise PublicationError("generator_revision must be a 40- or 64-character hexadecimal revision") + return PublicationSpec(claim_scope, filters, statistic, float(margin), revision) + + +def _filter_records( + records: tuple[RunBundleRecord, ...], + filters: dict[str, object], +) -> tuple[RunBundleRecord, ...]: + selected = records + for path, expected in sorted(filters.items()): + selected = tuple(record for record in selected if _record_value(record, path) == expected) + if not selected: + raise PublicationError("publication filters selected no committed bundles") + return selected + + +def _record_value(record: RunBundleRecord, path: str) -> object: + prefix, separator, key = path.partition(".") + if not separator or prefix not in {"config", "dataset", "environment", "platform", "runtime"} or not key: + raise PublicationError(f"invalid publication filter path: {path!r}") + document = getattr(record, prefix) + return document.get(key) + + +def _render(records: tuple[RunBundleRecord, ...], spec: PublicationSpec) -> dict[str, str]: + ordered = tuple(sorted(records, key=lambda record: record.run_key)) + table = { + "claim_scope": spec.claim_scope.value, + "groups": _group_rows(ordered, spec.statistic), + "practical_margin_percent": spec.practical_margin_percent, + "rows": [_result_row(record, spec.statistic) for record in ordered], + "schema_version": PUBLICATION_SCHEMA_VERSION, + "statistic": spec.statistic, + } + results_content = _json_content(table) + provenance_core = { + "bundle_ids": [record.bundle_id for record in ordered], + "bundle_keys": [record.run_key for record in ordered], + "claim_scope": spec.claim_scope.value, + "filters": spec.filters, + "generator_revision": spec.generator_revision, + "output_sha256": hashlib.sha256(results_content.encode()).hexdigest(), + "practical_margin_percent": spec.practical_margin_percent, + "schema_version": PUBLICATION_SCHEMA_VERSION, + "statistic": spec.statistic, + } + provenance = {**provenance_core, "publication_id": _digest_json(provenance_core)} + return { + "provenance.json": _json_content(provenance), + "results.json": results_content, + } + + +def _result_row(record: RunBundleRecord, statistic: str) -> dict[str, object]: + statistics = record.summary.get("statistics") + if not isinstance(statistics, dict) or not isinstance(statistics.get(statistic), dict): + raise PublicationError(f"bundle {record.run_key} has no statistic {statistic!r}") + summary = statistics[statistic] + return { + "block_position": record.config.get("block_position"), + "config_id": record.config.get("config_id"), + "decoder_id": record.config.get("decoder_id"), + "environment_id": record.environment.get("environment_id"), + "manifest_id": record.dataset.get("manifest_id"), + "mean": summary.get("mean"), + "median": summary.get("median"), + "n": summary.get("n"), + "platform_id": record.platform.get("platform_id"), + "platform_comparison_id": platform_comparison_id(record.platform), + "platform_location": platform_location(record.platform), + "protocol_id": record.config.get("protocol_id"), + "raw_samples": [ + _sample_value(sample.elapsed_seconds, sample.items_processed, statistic) for sample in record.samples + ], + "repetition": record.config.get("repetition"), + "run_key": record.run_key, + "sample_std": summary.get("sample_std"), + "support_set_id": record.dataset.get("support_set_id"), + "workload_id": record.dataset.get("workload_id"), + } + + +def _group_rows(records: tuple[RunBundleRecord, ...], statistic: str) -> list[dict[str, object]]: + groups: dict[tuple[object, ...], list[RunBundleRecord]] = {} + for record in records: + key = ( + record.config.get("plan_id"), + record.config.get("config_id"), + record.dataset.get("workload_id"), + record.dataset.get("support_set_id"), + record.environment.get("environment_id"), + platform_comparison_id(record.platform), + ) + groups.setdefault(key, []).append(record) + return [_group_row(groups[key], statistic) for key in sorted(groups, key=_sortable_group_key)] + + +def _sortable_group_key(key: tuple[object, ...]) -> tuple[str, ...]: + return tuple(str(value) for value in key) + + +def _group_row(records: list[RunBundleRecord], statistic: str) -> dict[str, object]: + ordered = sorted(records, key=lambda record: (record.config.get("repetition"), record.run_key)) + repetitions = [record.config.get("repetition") for record in ordered] + if len(repetitions) != len(set(repetitions)): + raise PublicationError("publication group contains duplicate repetition blocks") + values = [_run_mean(record, statistic) for record in ordered] + first = ordered[0] + excluded_config_fields = { + "block_position", + "plan_id", + "repetition", + "run_key", + "runner_revision", + "schema_version", + } + return { + "configuration": { + key: value for key, value in sorted(first.config.items()) if key not in excluded_config_fields + }, + "environment_id": first.environment.get("environment_id"), + "mean": statistics.fmean(values), + "median": statistics.median(values), + "n": len(values), + "platform_comparison_id": platform_comparison_id(first.platform), + "platform_ids": sorted({str(record.platform.get("platform_id")) for record in ordered}), + "platform_locations": sorted( + {location for record in ordered if (location := platform_location(record.platform)) is not None}, + ), + "raw_run_means": values, + "repetitions": repetitions, + "run_keys": [record.run_key for record in ordered], + "sample_std": statistics.stdev(values) if len(values) > 1 else None, + "support_set_id": first.dataset.get("support_set_id"), + "workload_id": first.dataset.get("workload_id"), + } + + +def _run_mean(record: RunBundleRecord, statistic: str) -> float: + values = [_sample_value(sample.elapsed_seconds, sample.items_processed, statistic) for sample in record.samples] + if not values: + raise PublicationError(f"bundle {record.run_key} contains no timed samples") + return statistics.fmean(values) + + +def _sample_value(elapsed_seconds: float, items_processed: int, statistic: str) -> float: + if statistic == "elapsed_seconds": + return elapsed_seconds + if statistic == "images_per_second": + return items_processed / elapsed_seconds + return elapsed_seconds * 1_000_000 / items_processed + + +def _check_outputs(destination: Path, generated: dict[str, str]) -> None: + observed = {path.name for path in destination.iterdir() if path.is_file()} if destination.is_dir() else set() + if observed != set(_OUTPUT_FILES): + raise PublicationError(f"publication output is stale; file set is {sorted(observed)}") + stale = [name for name, content in generated.items() if (destination / name).read_text() != content] + if stale: + raise PublicationError(f"publication output is stale: {', '.join(sorted(stale))}") + + +def _atomic_write(path: Path, content: str) -> None: + temporary: Path | None = None + try: + with tempfile.NamedTemporaryFile(mode="w", dir=path.parent, prefix=f".{path.name}.", delete=False) as file: + file.write(content) + temporary = Path(file.name) + temporary.replace(path) + finally: + if temporary is not None: + temporary.unlink(missing_ok=True) + + +def _required_string(payload: dict[str, Any], key: str) -> str: + value = payload.get(key) + if not isinstance(value, str) or not value: + raise PublicationError(f"publication field {key!r} must be a non-empty string") + return value + + +def _json_content(payload: object) -> str: + return json.dumps(payload, ensure_ascii=False, indent=2, sort_keys=True) + "\n" + + +def _digest_json(payload: object) -> str: + canonical = json.dumps(payload, ensure_ascii=False, separators=(",", ":"), sort_keys=True) + return hashlib.sha256(canonical.encode()).hexdigest() diff --git a/imread_benchmark/analysis/statistics.py b/imread_benchmark/analysis/statistics.py new file mode 100644 index 0000000..8efec6a --- /dev/null +++ b/imread_benchmark/analysis/statistics.py @@ -0,0 +1,69 @@ +from __future__ import annotations + +import math +import statistics +from dataclasses import dataclass +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from collections.abc import Sequence + + +@dataclass(frozen=True, slots=True) +class DistributionSummary: + n: int + mean: float + median: float + minimum: float + maximum: float + sample_std: float | None + coefficient_of_variation: float | None + + def to_dict(self) -> dict[str, float | int | None]: + return { + "coefficient_of_variation": self.coefficient_of_variation, + "maximum": self.maximum, + "mean": self.mean, + "median": self.median, + "minimum": self.minimum, + "n": self.n, + "sample_std": self.sample_std, + } + + +def summarize_distribution(values: Sequence[float]) -> DistributionSummary: + measurements = tuple(float(value) for value in values) + if not measurements or any(not math.isfinite(value) or value <= 0 for value in measurements): + raise ValueError("measurements must contain one or more finite positive values") + + mean = statistics.fmean(measurements) + sample_std = statistics.stdev(measurements) if len(measurements) > 1 else None + return DistributionSummary( + n=len(measurements), + mean=mean, + median=statistics.median(measurements), + minimum=min(measurements), + maximum=max(measurements), + sample_std=sample_std, + coefficient_of_variation=sample_std / mean if sample_std is not None else None, + ) + + +def summarize_benchmark( + elapsed_seconds: Sequence[float], + *, + items_processed: int, +) -> dict[str, dict[str, float | int | None]]: + if items_processed <= 0: + raise ValueError("items_processed must be positive") + elapsed = tuple(float(value) for value in elapsed_seconds) + elapsed_summary = summarize_distribution(elapsed) + images_per_second = summarize_distribution(tuple(items_processed / value for value in elapsed)) + microseconds_per_image = summarize_distribution( + tuple(value / items_processed * 1_000_000 for value in elapsed), + ) + return { + "elapsed_seconds": elapsed_summary.to_dict(), + "images_per_second": images_per_second.to_dict(), + "microseconds_per_image": microseconds_per_image.to_dict(), + } diff --git a/imread_benchmark/artifacts/__init__.py b/imread_benchmark/artifacts/__init__.py new file mode 100644 index 0000000..732a53b --- /dev/null +++ b/imread_benchmark/artifacts/__init__.py @@ -0,0 +1,29 @@ +from imread_benchmark.artifacts.bundle import ( + REMOTE_BUNDLE_FILES, + RUN_BUNDLE_FILES, + BundleData, + BundleValidationError, + RunSample, + validate_run_bundle, + write_run_bundle, +) +from imread_benchmark.artifacts.remote import ( + RemoteArtifactError, + hydrate_committed_runs, + publish_run_bundle, + pull_committed_run, +) + +__all__ = [ + "REMOTE_BUNDLE_FILES", + "RUN_BUNDLE_FILES", + "BundleData", + "BundleValidationError", + "RemoteArtifactError", + "RunSample", + "hydrate_committed_runs", + "publish_run_bundle", + "pull_committed_run", + "validate_run_bundle", + "write_run_bundle", +] diff --git a/imread_benchmark/artifacts/bundle.py b/imread_benchmark/artifacts/bundle.py new file mode 100644 index 0000000..cf942a8 --- /dev/null +++ b/imread_benchmark/artifacts/bundle.py @@ -0,0 +1,482 @@ +from __future__ import annotations + +import hashlib +import json +import math +import re +import shutil +import tempfile +from dataclasses import dataclass, fields +from pathlib import Path +from typing import Any + +from imread_benchmark.analysis.statistics import summarize_benchmark + +BUNDLE_SCHEMA_VERSION = "2.0" +_DIGEST = re.compile(r"^[0-9a-f]{64}$") +_DOCUMENT_FILES = ( + "config.json", + "dataset.json", + "environment.json", + "platform.json", + "runtime.json", + "samples.jsonl", + "failures.jsonl", + "events.jsonl", + "summary.json", +) +_ALL_FILES = frozenset((*_DOCUMENT_FILES, "bundle_manifest.json", "COMMITTED.json")) +RUN_BUNDLE_FILES = tuple(sorted(_ALL_FILES)) +REMOTE_BUNDLE_FILES = tuple(name for name in RUN_BUNDLE_FILES if name != "COMMITTED.json") +_RESERVED_SUMMARY_FIELDS = frozenset({"schema_version", "statistics", "status"}) + + +class BundleValidationError(ValueError): + pass + + +@dataclass(frozen=True, slots=True) +class RunSample: + sample_index: int + elapsed_seconds: float + items_processed: int + started_at_utc: str | None = None + + def __post_init__(self) -> None: + if self.sample_index < 0: + raise ValueError("sample_index must be non-negative") + if not math.isfinite(self.elapsed_seconds) or self.elapsed_seconds <= 0: + raise ValueError("elapsed_seconds must be finite and positive") + if self.items_processed <= 0: + raise ValueError("items_processed must be positive") + + def to_dict(self) -> dict[str, object]: + return { + "elapsed_seconds": self.elapsed_seconds, + "images_per_second": self.items_processed / self.elapsed_seconds, + "items_processed": self.items_processed, + "sample_index": self.sample_index, + "schema_version": BUNDLE_SCHEMA_VERSION, + "started_at_utc": self.started_at_utc, + } + + +@dataclass(frozen=True, slots=True) +class BundleData: + config: dict[str, object] + dataset: dict[str, object] + environment: dict[str, object] + platform: dict[str, object] + runtime: dict[str, object] + samples: tuple[RunSample, ...] + summary_fields: dict[str, object] + events: tuple[dict[str, object], ...] = () + + +def write_run_bundle( + *, + root: str | Path, + run_key: str, + data: BundleData, +) -> Path: + _validate_digest(run_key, field="run_key") + if not data.samples: + raise ValueError("run bundle requires at least one sample") + reserved = _RESERVED_SUMMARY_FIELDS.intersection(data.summary_fields) + if reserved: + raise ValueError(f"summary_fields contains reserved names: {', '.join(sorted(reserved))}") + + root_path = Path(root) + destination = root_path / run_key + if destination.exists(): + raise FileExistsError(f"run bundles are immutable: {destination}") + root_path.mkdir(parents=True, exist_ok=True) + staging = Path(tempfile.mkdtemp(prefix=f".{run_key}.", dir=root_path)) + try: + _write_payload(staging, data) + manifest = _build_bundle_manifest(staging, run_key) + _write_json(staging / "bundle_manifest.json", manifest) + _write_json( + staging / "COMMITTED.json", + { + "bundle_id": manifest["bundle_id"], + "run_key": run_key, + "schema_version": BUNDLE_SCHEMA_VERSION, + "status": "committed", + }, + ) + validate_run_bundle(staging, expected_run_key=run_key) + staging.rename(destination) + except Exception: + shutil.rmtree(staging, ignore_errors=True) + raise + return destination + + +def validate_run_bundle(bundle: str | Path, *, expected_run_key: str | None = None) -> None: + root = Path(bundle) + observed_files = {path.name for path in root.iterdir() if path.is_file()} if root.is_dir() else set() + if observed_files != _ALL_FILES: + missing = sorted(_ALL_FILES - observed_files) + extra = sorted(observed_files - _ALL_FILES) + raise BundleValidationError(f"run bundle file set mismatch; missing={missing}, extra={extra}") + + commit = _read_object(root / "COMMITTED.json") + manifest = _read_object(root / "bundle_manifest.json") + run_key = _required_digest(commit, "run_key") + if expected_run_key is not None and run_key != expected_run_key: + raise BundleValidationError("run_key does not match expected identity") + if commit.get("schema_version") != BUNDLE_SCHEMA_VERSION or commit.get("status") != "committed": + raise BundleValidationError("invalid commit marker") + if manifest.get("run_key") != run_key or manifest.get("schema_version") != BUNDLE_SCHEMA_VERSION: + raise BundleValidationError("bundle manifest identity mismatch") + + files = manifest.get("files") + if not isinstance(files, dict) or set(files) != set(_DOCUMENT_FILES): + raise BundleValidationError("bundle manifest file set mismatch") + manifest_core = {key: value for key, value in manifest.items() if key != "bundle_id"} + bundle_id = _digest_json(manifest_core) + if manifest.get("bundle_id") != bundle_id or commit.get("bundle_id") != bundle_id: + raise BundleValidationError("bundle_id mismatch") + _validate_payload_checksums(root, files) + _validate_cross_file_identity(root, run_key) + _validate_summary(root) + + +def _write_payload(root: Path, data: BundleData) -> None: + _write_json(root / "config.json", _versioned(data.config)) + _write_json(root / "dataset.json", _versioned(data.dataset)) + _write_json(root / "environment.json", _versioned(data.environment)) + _write_json(root / "platform.json", _versioned(data.platform)) + _write_json(root / "runtime.json", _versioned(data.runtime)) + _write_jsonl(root / "samples.jsonl", [sample.to_dict() for sample in data.samples]) + _write_jsonl(root / "failures.jsonl", []) + _write_jsonl(root / "events.jsonl", [_versioned(row) for row in data.events]) + items_processed = {sample.items_processed for sample in data.samples} + if len(items_processed) != 1: + raise ValueError("all samples in a run bundle must process the same number of items") + statistics = summarize_benchmark( + [sample.elapsed_seconds for sample in data.samples], + items_processed=items_processed.pop(), + ) + _write_json( + root / "summary.json", + { + **data.summary_fields, + "schema_version": BUNDLE_SCHEMA_VERSION, + "statistics": statistics, + "status": "complete", + }, + ) + + +def _build_bundle_manifest(root: Path, run_key: str) -> dict[str, object]: + core: dict[str, object] = { + "files": { + name: { + "bytes": (root / name).stat().st_size, + "sha256": _sha256_file(root / name), + } + for name in _DOCUMENT_FILES + }, + "run_key": run_key, + "schema_version": BUNDLE_SCHEMA_VERSION, + } + return {**core, "bundle_id": _digest_json(core)} + + +def _validate_payload_checksums(root: Path, files: dict[str, Any]) -> None: + for name in _DOCUMENT_FILES: + metadata = files.get(name) + if not isinstance(metadata, dict): + raise BundleValidationError(f"bundle manifest metadata missing for {name}") + path = root / name + if path.stat().st_size != metadata.get("bytes") or _sha256_file(path) != metadata.get("sha256"): + raise BundleValidationError(f"checksum mismatch for {name}") + + +def _validate_summary(root: Path) -> None: + config = _read_object(root / "config.json") + summary = _read_object(root / "summary.json") + samples = _read_jsonl(root / "samples.jsonl") + if _read_jsonl(root / "failures.jsonl"): + raise BundleValidationError("a committed successful run cannot contain timed failures") + expected_count = config.get("timed_passes_per_run") + if not isinstance(expected_count, int) or expected_count <= 0 or len(samples) != expected_count: + raise BundleValidationError("sample count does not match config.timed_passes_per_run") + indices = [sample.get("sample_index") for sample in samples] + if indices != list(range(len(samples))): + raise BundleValidationError("sample_index values are not consecutive") + elapsed = [sample.get("elapsed_seconds") for sample in samples] + item_counts = {sample.get("items_processed") for sample in samples} + if ( + any(isinstance(value, bool) or not isinstance(value, (int, float)) or value <= 0 for value in elapsed) + or len(item_counts) != 1 + ): + raise BundleValidationError("samples contain invalid measurements") + items_processed = next(iter(item_counts)) + if isinstance(items_processed, bool) or not isinstance(items_processed, int) or items_processed <= 0: + raise BundleValidationError("samples contain invalid items_processed") + expected_statistics = summarize_benchmark( + [float(value) for value in elapsed if isinstance(value, (int, float))], + items_processed=items_processed, + ) + if summary.get("schema_version") != BUNDLE_SCHEMA_VERSION or summary.get("status") != "complete": + raise BundleValidationError("summary identity mismatch") + if summary.get("statistics") != expected_statistics: + raise BundleValidationError("summary statistics do not match samples") + + +def _validate_cross_file_identity(root: Path, run_key: str) -> None: + from imread_benchmark.environments import load_environment_descriptor + from imread_benchmark.execution.spec import RunIdentity, compute_run_key + from imread_benchmark.plans import RunConfiguration + from imread_benchmark.platforms import load_platform_descriptor + + config = _read_object(root / "config.json") + dataset = _read_object(root / "dataset.json") + runtime = _read_object(root / "runtime.json") + summary = _read_object(root / "summary.json") + samples = _read_jsonl(root / "samples.jsonl") + try: + environment_descriptor = load_environment_descriptor(root / "environment.json") + platform_descriptor = load_platform_descriptor(root / "platform.json") + except (OSError, TypeError, ValueError) as exc: + raise BundleValidationError(f"invalid environment or platform descriptor: {exc}") from exc + configuration = _configuration_from_document(config, RunConfiguration) + if config.get("config_id") != configuration.config_id: + raise BundleValidationError("config_id does not match expanded configuration") + _require_equal_dataset_identity(configuration, dataset) + item_ids = _required_string_tuple(dataset, "ordered_item_ids") + try: + identity = RunIdentity( + plan_id=_required_string(config, "plan_id"), + platform_id=platform_descriptor.platform_id, + environment_id=environment_descriptor.environment_id, + runner_revision=_required_string(config, "runner_revision"), + workload_id=_required_string(dataset, "workload_id"), + support_set_id=_required_string(dataset, "support_set_id"), + support_item_ids=item_ids, + configuration=configuration, + repetition=_required_non_negative_int(config, "repetition"), + block_position=_required_non_negative_int(config, "block_position"), + ) + except BundleValidationError: + raise + except (KeyError, TypeError, ValueError) as exc: + raise BundleValidationError(f"invalid cross-file run identity: {exc}") from exc + if config.get("run_key") != run_key or compute_run_key(identity) != run_key: + raise BundleValidationError("run_key does not match cross-file run identity") + if environment_descriptor.runner_revision != identity.runner_revision: + raise BundleValidationError("environment runner revision does not match run identity") + _validate_measurement_counts(configuration, item_ids, samples, summary) + _validate_runtime(configuration, runtime) + _validate_events(root) + + +def _configuration_from_document(config: dict[str, Any], configuration_type: Any) -> Any: + try: + return configuration_type( + **{field.name: config[field.name] for field in fields(configuration_type)}, + ) + except (KeyError, TypeError, ValueError) as exc: + raise BundleValidationError(f"invalid expanded run configuration: {exc}") from exc + + +def _require_equal_dataset_identity(configuration: Any, dataset: dict[str, Any]) -> None: + expected = { + "manifest_id": configuration.manifest_id, + "package_id": configuration.package_id, + "selection_id": configuration.selection_id, + "support_policy": configuration.support_policy, + } + mismatched = [key for key, value in expected.items() if dataset.get(key) != value] + if mismatched: + raise BundleValidationError(f"config and dataset identity mismatch: {mismatched}") + expected_context = ( + "dataloader" if configuration.protocol_id == "loader-supply" and configuration.num_workers else "main-process" + ) + if dataset.get("support_process_context") != expected_context: + raise BundleValidationError("support process context does not match protocol") + if dataset.get("support_multiprocessing_start_method") != configuration.multiprocessing_start_method: + raise BundleValidationError("support multiprocessing start method does not match configuration") + + +def _validate_measurement_counts( + configuration: Any, + item_ids: tuple[str, ...], + samples: list[dict[str, Any]], + summary: dict[str, Any], +) -> None: + logical_items = len(item_ids) * configuration.logical_repeat_factor + if any(sample.get("items_processed") != logical_items for sample in samples): + raise BundleValidationError("sample items_processed does not match pinned support and logical repeats") + if ( + summary.get("num_unique_images") != len(item_ids) + or summary.get("logical_repeat_factor") != configuration.logical_repeat_factor + or summary.get("logical_decodes_per_pass") != logical_items + ): + raise BundleValidationError("summary item counts do not match pinned support and logical repeats") + + +def _validate_runtime(configuration: Any, runtime: dict[str, Any]) -> None: + requested = configuration.requested_threads + if configuration.protocol_id == "decode-memory": + _validate_effective_threads(runtime.get("effective_threads"), requested) + return + _validate_loader_runtime(configuration, runtime, requested) + + +def _validate_loader_runtime(configuration: Any, runtime: dict[str, Any], requested: int | None) -> None: + handshakes = runtime.get("worker_handshakes") + expected_processes = max(1, configuration.num_workers) + expected_start_method = ( + "in-process" if configuration.num_workers == 0 else configuration.multiprocessing_start_method + ) + if runtime.get("multiprocessing_start_method") != expected_start_method: + raise BundleValidationError("runtime multiprocessing start method does not match configuration") + if not isinstance(handshakes, list) or len(handshakes) != expected_processes: + raise BundleValidationError("runtime does not contain the expected DataLoader worker handshakes") + process_ids = _validate_worker_handshakes( + handshakes, + expected_processes=expected_processes, + expected_start_method=expected_start_method, + requested_threads=requested, + ) + if runtime.get("persistent_workers_reused") is not True: + raise BundleValidationError("runtime did not confirm DataLoader process reuse") + coordinator_process_id = runtime.get("process_id") + if isinstance(coordinator_process_id, bool) or not isinstance(coordinator_process_id, int): + raise BundleValidationError("runtime process_id must be an integer") + if configuration.num_workers == 0 and process_ids != {coordinator_process_id}: + raise BundleValidationError("workers=0 handshake must come from the run process") + if configuration.num_workers and coordinator_process_id in process_ids: + raise BundleValidationError("DataLoader worker handshake came from the run process") + + +def _validate_worker_handshakes( + handshakes: list[object], + *, + expected_processes: int, + expected_start_method: str | None, + requested_threads: int | None, +) -> set[int]: + process_ids: set[int] = set() + for handshake in handshakes: + if not isinstance(handshake, dict): + raise BundleValidationError("DataLoader worker handshake must be an object") + process_id = handshake.get("process_id") + if isinstance(process_id, bool) or not isinstance(process_id, int) or process_id <= 0: + raise BundleValidationError("DataLoader worker handshake has an invalid process_id") + process_ids.add(process_id) + _validate_effective_threads(handshake.get("effective_threads"), requested_threads) + if handshake.get("multiprocessing_start_method") != expected_start_method: + raise BundleValidationError("DataLoader worker start method does not match configuration") + if len(process_ids) != expected_processes: + raise BundleValidationError("DataLoader worker handshakes contain duplicate process IDs") + return process_ids + + +def _validate_effective_threads(effective: object, requested: int | None) -> None: + if isinstance(effective, bool) or not isinstance(effective, int) or effective <= 0: + raise BundleValidationError("runtime effective_threads must be a positive integer") + if requested is not None and effective != requested: + raise BundleValidationError("runtime effective_threads does not match requested_threads") + + +def _validate_events(root: Path) -> None: + events = _read_jsonl(root / "events.jsonl") + event_names = {event.get("event") for event in events} + required = {"validation", "warmup", "measurement_complete"} + if not required.issubset(event_names): + raise BundleValidationError(f"run events are missing required phases: {sorted(required - event_names)}") + + +def _required_string(payload: dict[str, Any], key: str) -> str: + value = payload.get(key) + if not isinstance(value, str) or not value: + raise BundleValidationError(f"field {key!r} must be a non-empty string") + return value + + +def _required_string_tuple(payload: dict[str, Any], key: str) -> tuple[str, ...]: + value = payload.get(key) + if not isinstance(value, list) or not value or not all(isinstance(item, str) and item for item in value): + raise BundleValidationError(f"field {key!r} must be a non-empty string list") + result = tuple(value) + if len(set(result)) != len(result): + raise BundleValidationError(f"field {key!r} must contain unique values") + return result + + +def _required_non_negative_int(payload: dict[str, Any], key: str) -> int: + value = payload.get(key) + if isinstance(value, bool) or not isinstance(value, int) or value < 0: + raise BundleValidationError(f"field {key!r} must be a non-negative integer") + return value + + +def _versioned(payload: dict[str, object]) -> dict[str, object]: + if "schema_version" in payload and payload["schema_version"] != BUNDLE_SCHEMA_VERSION: + raise ValueError("payload has an incompatible schema_version") + return {**payload, "schema_version": BUNDLE_SCHEMA_VERSION} + + +def _validate_digest(value: str, *, field: str) -> None: + if _DIGEST.fullmatch(value) is None: + raise ValueError(f"{field} must be a lowercase SHA-256 digest") + + +def _required_digest(payload: dict[str, Any], key: str) -> str: + value = payload.get(key) + if not isinstance(value, str) or _DIGEST.fullmatch(value) is None: + raise BundleValidationError(f"{key} must be a lowercase SHA-256 digest") + return value + + +def _read_object(path: Path) -> dict[str, Any]: + try: + value = json.loads(path.read_text()) + except (OSError, json.JSONDecodeError) as exc: + raise BundleValidationError(f"cannot read {path.name}: {exc}") from exc + if not isinstance(value, dict): + raise BundleValidationError(f"{path.name} must contain a JSON object") + return value + + +def _read_jsonl(path: Path) -> list[dict[str, Any]]: + rows: list[dict[str, Any]] = [] + try: + lines = path.read_text().splitlines() + except OSError as exc: + raise BundleValidationError(f"cannot read {path.name}: {exc}") from exc + for line_number, line in enumerate(lines, start=1): + try: + value = json.loads(line) + except json.JSONDecodeError as exc: + raise BundleValidationError(f"invalid JSON at {path.name}:{line_number}") from exc + if not isinstance(value, dict): + raise BundleValidationError(f"{path.name}:{line_number} must contain an object") + rows.append(value) + return rows + + +def _write_json(path: Path, payload: object) -> None: + path.write_text(json.dumps(payload, ensure_ascii=False, indent=2, sort_keys=True) + "\n") + + +def _write_jsonl(path: Path, rows: list[dict[str, object]]) -> None: + path.write_text("".join(json.dumps(row, ensure_ascii=False, sort_keys=True) + "\n" for row in rows)) + + +def _digest_json(payload: object) -> str: + canonical = json.dumps(payload, ensure_ascii=False, separators=(",", ":"), sort_keys=True) + return hashlib.sha256(canonical.encode()).hexdigest() + + +def _sha256_file(path: Path) -> str: + digest = hashlib.sha256() + with path.open("rb") as file: + while chunk := file.read(1024 * 1024): + digest.update(chunk) + return digest.hexdigest() diff --git a/imread_benchmark/artifacts/remote.py b/imread_benchmark/artifacts/remote.py new file mode 100644 index 0000000..5561f98 --- /dev/null +++ b/imread_benchmark/artifacts/remote.py @@ -0,0 +1,142 @@ +from __future__ import annotations + +import json +import shutil +import tempfile +from pathlib import Path +from typing import Any + +from imread_benchmark.artifacts.bundle import REMOTE_BUNDLE_FILES, validate_run_bundle +from imread_benchmark.datasets.materializer import LocalObjectStore, ObjectNotFoundError, ObjectStore + + +class RemoteArtifactError(RuntimeError): + pass + + +def publish_run_bundle( + bundle: str | Path, + *, + store: ObjectStore, + prefix: str = "artifacts", +) -> None: + bundle_path = Path(bundle).resolve() + commit = _read_object(bundle_path / "COMMITTED.json") + run_key = _required_string(commit, "run_key") + bundle_id = _required_string(commit, "bundle_id") + validate_run_bundle(bundle_path, expected_run_key=run_key) + for name in REMOTE_BUNDLE_FILES: + store.put_create_only(bundle_path / name, f"{prefix}/bundles/{bundle_id}/{name}") + store.put_create_only(bundle_path / "COMMITTED.json", f"{prefix}/runs/{run_key}/COMMITTED.json") + + +def pull_committed_run( + run_key: str, + *, + store: ObjectStore, + artifact_root: str | Path, + prefix: str = "artifacts", +) -> Path | None: + root = Path(artifact_root).resolve() + destination = root / "runs" / run_key + if destination.exists(): + validate_run_bundle(destination, expected_run_key=run_key) + return destination + root.mkdir(parents=True, exist_ok=True) + staging_root = Path(tempfile.mkdtemp(prefix=f".pull-{run_key}.", dir=root)) + bundle_staging = staging_root / run_key + bundle_staging.mkdir() + try: + return _pull_into_staging( + run_key, + store=store, + prefix=prefix, + staging=bundle_staging, + destination=destination, + ) + except RemoteArtifactError: + raise + except (OSError, TypeError, ValueError) as exc: + raise RemoteArtifactError(f"cannot materialize committed run {run_key}: {exc}") from exc + finally: + shutil.rmtree(staging_root, ignore_errors=True) + + +def hydrate_committed_runs( + *, + source_artifact_root: str | Path, + destination_artifact_root: str | Path, +) -> tuple[Path, ...]: + """Materialize a downloaded remote artifact layout into local run bundles.""" + source_root = Path(source_artifact_root).resolve() + destination_root = Path(destination_artifact_root).resolve() + markers_root = source_root / "runs" + if not markers_root.is_dir(): + raise RemoteArtifactError(f"downloaded artifact root has no runs directory: {source_root}") + if source_root == destination_root: + raise RemoteArtifactError("source and destination artifact roots must differ") + + store = LocalObjectStore(source_root.parent) + hydrated: list[Path] = [] + for marker in sorted(markers_root.glob("*/COMMITTED.json")): + run_key = marker.parent.name + bundle = pull_committed_run( + run_key, + store=store, + artifact_root=destination_root, + prefix=source_root.name, + ) + if bundle is None: + raise RemoteArtifactError(f"downloaded commit marker disappeared for run {run_key}") + hydrated.append(bundle) + if not hydrated: + raise RemoteArtifactError(f"downloaded artifact root has no committed runs: {source_root}") + return tuple(hydrated) + + +def _pull_into_staging( + run_key: str, + *, + store: ObjectStore, + prefix: str, + staging: Path, + destination: Path, +) -> Path | None: + marker_key = f"{prefix}/runs/{run_key}/COMMITTED.json" + try: + store.download(marker_key, staging / "COMMITTED.json") + except ObjectNotFoundError: + return None + commit = _read_object(staging / "COMMITTED.json") + if _required_string(commit, "run_key") != run_key: + raise RemoteArtifactError("remote commit marker run_key mismatch") + bundle_id = _required_string(commit, "bundle_id") + for name in REMOTE_BUNDLE_FILES: + try: + store.download(f"{prefix}/bundles/{bundle_id}/{name}", staging / name) + except ObjectNotFoundError as exc: + raise RemoteArtifactError(f"committed remote bundle is missing {name}") from exc + validate_run_bundle(staging, expected_run_key=run_key) + destination.parent.mkdir(parents=True, exist_ok=True) + try: + staging.rename(destination) + except FileExistsError: + validate_run_bundle(destination, expected_run_key=run_key) + return destination + + +def _read_object(path: Path) -> dict[str, Any]: + try: + value = json.loads(path.read_text()) + except (OSError, json.JSONDecodeError) as exc: + raise RemoteArtifactError(f"cannot read {path.name}: {exc}") from exc + if not isinstance(value, dict): + raise TypeError(f"{path.name} must contain a JSON object") + return value + + +def _required_string(payload: dict[str, Any], key: str) -> str: + value = payload.get(key) + if not isinstance(value, str) or not value: + raise RemoteArtifactError(f"remote artifact field {key!r} must be a non-empty string") + return value diff --git a/imread_benchmark/benchmark.py b/imread_benchmark/benchmark.py deleted file mode 100644 index 9f596fa..0000000 --- a/imread_benchmark/benchmark.py +++ /dev/null @@ -1,145 +0,0 @@ -from __future__ import annotations - -import gc -import time -from typing import TYPE_CHECKING, Any - -if TYPE_CHECKING: - from collections.abc import Callable, Sequence - -import numpy as np -from tqdm import tqdm - -# Bound on retained error strings — enough to triage in the JSON without -# bloating the file when a decoder rejects a large slice of the dataset. -MAX_SKIP_EXAMPLES = 3 - -# If a decoder fails on every single item it's not "decoder X has a 100% skip -# rate", it's "decoder X is fundamentally broken in this venv / on this build" -# — surface as an exception so the CLI marks the run as failed, not as a -# valid result with zero throughput. -_ALL_FAILED_TEMPLATE = ( - "Decoder failed on all {n} items during discovery pass. " - "First error: {err}. This is treated as a decoder failure, not a skip rate." -) - - -def _summarise(times_per_run_s: list[float], n_items: int) -> dict[str, Any]: - """ - Convert per-run wall times into summary statistics. - - Two units kept side by side: - - images_per_second_* — throughput (paper headlines) - - us_per_image_* — per-item latency (cleaner for cross-set comparison) - - Percentiles are over the per-run distribution (small N, but right-skewed - enough that p50 is meaningfully different from mean). - """ - times_arr = np.asarray(times_per_run_s, dtype=np.float64) - ips_arr = n_items / times_arr - us_arr = times_arr / n_items * 1e6 - - return { - "images_per_second_mean": float(ips_arr.mean()), - "images_per_second_std": float(ips_arr.std()), - "images_per_second_p50": float(np.percentile(ips_arr, 50)), - "images_per_second_p90": float(np.percentile(ips_arr, 90)), - "images_per_second_p99": float(np.percentile(ips_arr, 99)), - "us_per_image_mean": float(us_arr.mean()), - "us_per_image_p50": float(np.percentile(us_arr, 50)), - "us_per_image_p90": float(np.percentile(us_arr, 90)), - "us_per_image_p99": float(np.percentile(us_arr, 99)), - "raw_times_s": times_arr.tolist(), - "raw_throughput_ips": ips_arr.tolist(), - } - - -def _discover_skips( - decode_fn: Callable[[Any], np.ndarray], - items: Sequence[Any], -) -> tuple[list[int], list[str]]: - """ - Single un-timed pass to identify items the decoder cannot handle. - - Real ImageNet val contains a handful of non-standard JPEGs (CMYK, RGBA- - embedded, weird subsampling) that strict decoders like turbojpeg, jpeg4py, - and kornia-rs refuse with errors like "Unsupported color conversion - request". Tolerant decoders (Pillow, OpenCV, scikit-image) silently - convert. Treating these as fatal would bury 99.99% of clean numbers under - a single bad image, so we identify them up front and skip them in every - subsequent pass — and report skip count + sample errors in the JSON so - the paper can surface decoder robustness as a real property. - """ - skipped: list[int] = [] - examples: list[str] = [] - for idx, item in enumerate(items): - try: - decode_fn(item) - # Decoder libs raise everything from OSError to ValueError to custom - # exception types (jpeg4py.JPEGRuntimeError). We genuinely want to - # catch them all — anything that prevents successful decode of one - # item is a "skip", regardless of how the library chose to express it. - except Exception as exc: - skipped.append(idx) - if len(examples) < MAX_SKIP_EXAMPLES: - examples.append(f"idx={idx}: {type(exc).__name__}: {exc}") - return skipped, examples - - -def run_timing_loop( - decode_fn: Callable[[Any], np.ndarray], - items: Sequence[Any], - num_runs: int, - num_warmup: int = 2, -) -> dict[str, Any]: - """ - Time `decode_fn` over all `items`, `num_runs` rounds, with `num_warmup` untimed rounds first. - - items is either list[bytes] (memory mode) or list[str] (disk mode). - GC is disabled inside each timed pass to avoid mid-run pauses skewing tail latencies. - - Bad items (those that raise during decode — typically <0.1% of ImageNet val - for strict decoders) are identified in a single discovery pass and excluded - from every subsequent pass. Skip counts and sample errors are returned in - the result so the paper can report decoder robustness honestly. - """ - skipped_indices, skip_examples = _discover_skips(decode_fn, items) - n_total = len(items) - n_skipped = len(skipped_indices) - n_good = n_total - n_skipped - - if n_good == 0: - first_err = skip_examples[0] if skip_examples else "no example captured" - raise RuntimeError(_ALL_FAILED_TEMPLATE.format(n=n_total, err=first_err)) - - skip_set = set(skipped_indices) - good_items = [it for i, it in enumerate(items) if i not in skip_set] - - # The discovery pass already consumed one warmup's worth of work; subtract - # one so we don't double-pay (and don't go negative). - remaining_warmup = max(0, num_warmup - 1) - for _ in range(remaining_warmup): - for item in good_items: - decode_fn(item) - - times: list[float] = [] - for _ in tqdm(range(num_runs), desc="Benchmarking"): - gc.collect() - gc.disable() - try: - t0 = time.perf_counter() - for item in good_items: - decode_fn(item) - elapsed = time.perf_counter() - t0 - finally: - gc.enable() - times.append(elapsed) - - summary = _summarise(times, n_good) - summary["num_images_total"] = n_total - summary["num_images_decoded"] = n_good - summary["num_images_skipped"] = n_skipped - summary["skip_rate"] = n_skipped / n_total if n_total else 0.0 - summary["skip_indices"] = skipped_indices - summary["skip_examples"] = skip_examples - return summary diff --git a/imread_benchmark/benchmark_dataloader.py b/imread_benchmark/benchmark_dataloader.py deleted file mode 100644 index ae65a25..0000000 --- a/imread_benchmark/benchmark_dataloader.py +++ /dev/null @@ -1,215 +0,0 @@ -r""" -DataLoader throughput benchmark. - -Measures end-to-end throughput (images/sec) when a decoder is used inside a -PyTorch DataLoader with varying numbers of worker processes. Each worker -decodes from pre-loaded in-memory bytes, so disk I/O is excluded. - -Usage: - python -m imread_benchmark.benchmark_dataloader \ - --library opencv \ - --data-dir /path/to/imagenet/val \ - --output-dir output -""" - -from __future__ import annotations - -import argparse -import gc -import json -import logging -import time -from pathlib import Path -from typing import TYPE_CHECKING, Any - -if TYPE_CHECKING: - from collections.abc import Callable - -import numpy as np -from tqdm import tqdm - -from imread_benchmark.benchmark import _discover_skips, _summarise -from imread_benchmark.decoders import REGISTRY -from imread_benchmark.utils import collect_jpeg_paths, get_package_versions, get_system_identifier - -logging.basicConfig(level=logging.INFO) -logger = logging.getLogger(__name__) - -DEFAULT_WORKERS = [0, 1, 2, 4, 8] -BATCH_SIZE = 32 - - -class InMemoryDataset: - """Holds raw JPEG bytes; decodes on __getitem__ using the given decode_fn.""" - - def __init__(self, images_bytes: list[bytes], decode_fn: Callable[[bytes], Any]) -> None: - self.images_bytes = images_bytes - self.decode_fn = decode_fn - - def __len__(self) -> int: - return len(self.images_bytes) - - def __getitem__(self, idx: int) -> np.ndarray: - return self.decode_fn(self.images_bytes[idx]) - - -def _collate(batch: list[np.ndarray]) -> list[np.ndarray]: - # Images have variable resolutions — don't stack, just pass through. - # We only care about decode throughput, not tensor batching. - return batch - - -def benchmark_workers( - dataset: InMemoryDataset, - num_workers: int, - num_runs: int, - num_warmup: int = 1, -) -> dict[str, Any]: - """ - Iterate `dataset` through a DataLoader `num_runs` times and return throughput stats. - - Warmup is critical for `num_workers > 0`: spawning worker processes (and on - Linux fork() then re-importing libs in the worker) is wall-clock dominant - on the first iteration and would skew the mean / p50. - """ - from torch.utils.data import DataLoader - - loader = DataLoader( - dataset, - batch_size=BATCH_SIZE, - num_workers=num_workers, - collate_fn=_collate, - pin_memory=False, - persistent_workers=(num_workers > 0), - ) - - for _ in range(num_warmup): - for _ in loader: - pass - - times_s: list[float] = [] - n_images = len(dataset) - - for _ in tqdm(range(num_runs), desc=f"workers={num_workers}"): - gc.collect() - gc.disable() - try: - t0 = time.perf_counter() - for _ in loader: - pass - elapsed = time.perf_counter() - t0 - finally: - gc.enable() - times_s.append(elapsed) - - return { - "num_workers": num_workers, - "num_warmup": num_warmup, - **_summarise(times_s, n_images), - } - - -def main() -> None: - parser = argparse.ArgumentParser(description="DataLoader throughput benchmark for JPEG decoding.") - parser.add_argument("-l", "--library", required=True, help=f"One of: {', '.join(sorted(REGISTRY))}") - parser.add_argument("-d", "--data-dir", required=True) - parser.add_argument("-n", "--num-images", type=int, default=2000) - parser.add_argument("-r", "--num-runs", type=int, default=5) - parser.add_argument("-o", "--output-dir", type=Path, required=True) - parser.add_argument( - "--workers", - nargs="+", - type=int, - default=DEFAULT_WORKERS, - metavar="N", - help=f"num_workers values to benchmark (default: {DEFAULT_WORKERS})", - ) - args = parser.parse_args() - - library = args.library - decoder_cls = REGISTRY.get(library) - if decoder_cls is None: - parser.error(f"Unknown library '{library}'. Supported: {', '.join(REGISTRY)}") - - decoder = decoder_cls() - logger.info("DataLoader benchmark: %s", library) - - image_paths = collect_jpeg_paths(args.data_dir, args.num_images) - if not image_paths: - parser.error(f"No JPEG images found in {args.data_dir}") - - logger.info("Pre-loading %d images into memory…", len(image_paths)) - images_bytes = [p.read_bytes() for p in image_paths] - - # Filter out items the decoder cannot handle (CMYK / RGBA / weird subsampling - # JPEGs that exist in real ImageNet val). Doing this once on the main - # process, before spawning DataLoader workers, means: - # 1. Worker subprocesses never raise mid-batch and tear down the loader. - # 2. Every num_workers config is benchmarked on the exact same item set, - # so the comparison across worker counts stays apples-to-apples. - skipped_indices, skip_examples = _discover_skips(decoder.decode, images_bytes) - if len(skipped_indices) == len(images_bytes): - first_err = skip_examples[0] if skip_examples else "no example captured" - msg = f"Decoder {library!r} failed on all {len(images_bytes)} items. First error: {first_err}" - raise RuntimeError(msg) - if skipped_indices: - logger.warning( - "Decoder %s skipped %d/%d items (%.4f%%): %s", - library, - len(skipped_indices), - len(images_bytes), - 100.0 * len(skipped_indices) / len(images_bytes), - "; ".join(skip_examples), - ) - skip_set = set(skipped_indices) - good_bytes = [b for i, b in enumerate(images_bytes) if i not in skip_set] - - dataset = InMemoryDataset(good_bytes, decoder.decode) - - system_id = get_system_identifier() - output_dir = args.output_dir / system_id - output_dir.mkdir(parents=True, exist_ok=True) - output_file = output_dir / f"{library}_dataloader_results.json" - - results: dict[str, Any] = { - "library": library, - "benchmark_type": "dataloader", - "batch_size": BATCH_SIZE, - "num_threads": decoder.get_num_threads(), - "system_info": get_package_versions(library), - "worker_results": [], - # `num_images` retained as the count actually fed to the DataLoader - # (post-skip), so backwards-compatible plotting (which divides total - # time by num_images for throughput sanity-checks) keeps working. - "num_images": len(good_bytes), - "num_images_total": len(image_paths), - "num_images_decoded": len(good_bytes), - "num_images_skipped": len(skipped_indices), - "skip_rate": len(skipped_indices) / len(image_paths) if image_paths else 0.0, - "skip_indices": skipped_indices, - "skip_examples": skip_examples, - "num_runs": args.num_runs, - } - - # Write after every num_workers config so a hang on a later config (e.g. - # pyvips fork-deadlock at num_workers=8) doesn't lose the earlier results. - # The background `gcloud storage rsync` in vm_startup.sh ships these files - # to GCS within 30s of being written, so progress is durable. - for n in args.workers: - logger.info("Benchmarking with num_workers=%d", n) - result = benchmark_workers(dataset, n, args.num_runs) - results["worker_results"].append(result) - with output_file.open("w") as f: - json.dump(results, f, indent=2) - logger.info( - "Saved progress (%d/%d configs) to %s", - len(results["worker_results"]), - len(args.workers), - output_file, - ) - - logger.info("Results saved to %s", output_file) - - -if __name__ == "__main__": - main() diff --git a/imread_benchmark/benchmark_single.py b/imread_benchmark/benchmark_single.py deleted file mode 100644 index b7b9140..0000000 --- a/imread_benchmark/benchmark_single.py +++ /dev/null @@ -1,115 +0,0 @@ -from __future__ import annotations - -import argparse -import json -import logging -from pathlib import Path -from typing import TYPE_CHECKING, Any - -if TYPE_CHECKING: - from collections.abc import Callable - - import numpy as np - -from imread_benchmark.benchmark import run_timing_loop -from imread_benchmark.decoders import REGISTRY -from imread_benchmark.utils import collect_jpeg_paths, get_package_versions, get_system_identifier - -logging.basicConfig(level=logging.INFO) -logger = logging.getLogger(__name__) - - -def main() -> None: - parser = argparse.ArgumentParser(description="Benchmark a single image-decoding library.") - parser.add_argument("-l", "--library", required=True, help=f"One of: {', '.join(sorted(REGISTRY))}") - parser.add_argument("-d", "--data-dir", required=True, help="Directory containing JPEG images") - parser.add_argument("-n", "--num-images", type=int, default=2000) - parser.add_argument("-r", "--num-runs", type=int, default=5) - parser.add_argument("-o", "--output-dir", type=Path, required=True) - parser.add_argument( - "--mode", - choices=["memory", "disk"], - default="memory", - help="memory: decode from pre-loaded bytes (no I/O in timed loop); disk: decode from file path", - ) - parser.add_argument( - "--num-threads", - type=int, - default=0, - metavar="N", - help="threads for the library to use (0 = library default)", - ) - args = parser.parse_args() - - library = args.library - decoder_cls = REGISTRY.get(library) - if decoder_cls is None: - parser.error(f"Unknown library '{library}'. Supported: {', '.join(REGISTRY)}") - - decoder = decoder_cls() - requested_threads = args.num_threads # 0 means "library default" - if requested_threads > 0: - decoder.set_num_threads(requested_threads) - effective_threads = decoder.get_num_threads() - if requested_threads > 0 and effective_threads != requested_threads: - logger.warning( - "%s ignored set_num_threads(%d): library reports %d threads. " - "Likely the build uses a parallelism backend (e.g. Apple GCD) that ignores the request.", - library, - requested_threads, - effective_threads, - ) - logger.info( - "Benchmarking %s in %s mode, requested=%s effective=%d", - library, - args.mode, - "default" if requested_threads == 0 else str(requested_threads), - effective_threads, - ) - - image_paths = collect_jpeg_paths(args.data_dir, args.num_images) - if not image_paths: - parser.error(f"No JPEG images found in {args.data_dir}") - logger.info("Found %d images", len(image_paths)) - - decode_fn: Callable[[Any], np.ndarray] - items: list[Any] - if args.mode == "memory": - logger.info("Pre-loading images into memory…") - items = [p.read_bytes() for p in image_paths] - decode_fn = decoder.decode - else: - items = [str(p) for p in image_paths] - decode_fn = decoder.decode_path - - benchmark_results = run_timing_loop(decode_fn, items, args.num_runs) - - system_id = get_system_identifier() - output_dir = args.output_dir / system_id - output_dir.mkdir(parents=True, exist_ok=True) - - # Filename uses the REQUESTED mode (1t vs default), not the effective thread - # count. Otherwise libraries that ignore set_num_threads (opencv on macOS is - # the canonical case) collide with their own default-thread pass. - run_tag = "1t" if requested_threads == 1 else "default" if requested_threads == 0 else f"{requested_threads}t" - - results = { - "library": library, - "mode": args.mode, - "run_tag": run_tag, - "requested_threads": requested_threads, # 0 = library default - "effective_threads": effective_threads, # what the library actually used - "system_info": get_package_versions(library), - "benchmark_results": benchmark_results, - "num_images": len(image_paths), - "num_runs": args.num_runs, - } - - output_file = output_dir / f"{library}_{run_tag}_results.json" - with output_file.open("w") as f: - json.dump(results, f, indent=2) - logger.info("Results saved to %s", output_file) - - -if __name__ == "__main__": - main() diff --git a/imread_benchmark/cli.py b/imread_benchmark/cli.py index 0c58b04..611518f 100644 --- a/imread_benchmark/cli.py +++ b/imread_benchmark/cli.py @@ -1,412 +1,379 @@ -""" -imread-benchmark CLI — orchestrator that owns venv setup + benchmark dispatch. - -Replaces run_benchmarks.sh / run_dataloader_benchmarks.sh. Lives in the -"control plane" venv (anywhere with this package installed); shells out to -per-group worker venvs under venvs// for the actual decode work. -This split is required because mainstream / tensorflow cannot coexist -in one Python process (numpy / protobuf pin fights). - -Usage: - imread-benchmark list-libs - imread-benchmark run --data-dir ~/imagenet/val - imread-benchmark run --data-dir DATA --mode single --libs opencv,pillow - imread-benchmark plot --input output --output docs/assets/benchmarks -""" - from __future__ import annotations -import datetime as _dt import json -import os -import platform -import shutil -import subprocess import sys +from dataclasses import asdict from pathlib import Path import typer -from imread_benchmark.decoders import REGISTRY, BaseDecoder -from imread_benchmark.utils import get_system_identifier - -app = typer.Typer(add_completion=False, help="JPEG decoder benchmark orchestrator.") - -VENV_ROOT = Path("venvs") -DEFAULT_WORKERS = "0,1,2,4,8" - - -# ─── lib / group helpers ────────────────────────────────────────────────────── - - -def _all_lib_names() -> list[str]: - return sorted(REGISTRY.keys()) - - -def _resolve_libs(libs: str) -> list[str]: - if libs.strip() in {"", "all"}: - return _all_lib_names() - requested = [s.strip() for s in libs.split(",") if s.strip()] - unknown = [r for r in requested if r not in REGISTRY] - if unknown: - typer.echo(f"Unknown library names: {', '.join(unknown)}", err=True) - typer.echo(f"Known: {', '.join(_all_lib_names())}", err=True) - raise typer.Exit(2) - return requested - - -def _group_of(name: str) -> str: - return REGISTRY[name].group - - -def _runs_single(name: str) -> bool: - return REGISTRY[name].runs_single_here() - - -def _runs_dataloader(name: str) -> bool: - return REGISTRY[name].runs_dataloader_here() - - -# ─── venv management ────────────────────────────────────────────────────────── - - -def _venv_python(group: str) -> Path: - """Path to the python interpreter inside a per-group venv.""" - bindir = "Scripts" if platform.system() == "Windows" else "bin" - suffix = ".exe" if platform.system() == "Windows" else "" - return VENV_ROOT / group / bindir / f"python{suffix}" - +from imread_benchmark.analysis.publication import publish +from imread_benchmark.artifacts import hydrate_committed_runs, validate_run_bundle +from imread_benchmark.datasets.fodb import prepare_fodb +from imread_benchmark.datasets.gcs import GcloudObjectStore +from imread_benchmark.datasets.materializer import materialize_dataset_package, publish_dataset_package +from imread_benchmark.datasets.package import build_dataset_package +from imread_benchmark.decoders import REGISTRY +from imread_benchmark.decoders.capabilities import describe_decoder +from imread_benchmark.environments import EnvironmentRequest, provision_environment +from imread_benchmark.environments.cache import materialize_environment_cache, publish_environment_cache +from imread_benchmark.execution.campaign import CampaignConfig, run_campaign +from imread_benchmark.execution.coordinator import RemoteCheckpoint +from imread_benchmark.plans import expand_experiment_plan, instantiate_experiment_plans, load_experiment_plan +from imread_benchmark.platforms import capture_current_platform, write_platform_descriptor + +app = typer.Typer(add_completion=False, help="Reproducible JPEG decoder benchmark campaigns.") +dataset_app = typer.Typer(help="Build, publish, and materialize immutable dataset packages.") +plan_app = typer.Typer(help="Instantiate, validate, and expand schema-2 experiment plans.") +environment_app = typer.Typer(help="Provision immutable lock-backed worker environments.") +platform_app = typer.Typer(help="Capture platform provenance.") +artifacts_app = typer.Typer(help="Hydrate and validate committed run bundles.") +campaign_app = typer.Typer(help="Run or resume an isolated benchmark campaign.") +app.add_typer(dataset_app, name="dataset") +app.add_typer(plan_app, name="plan") +app.add_typer(environment_app, name="environment") +app.add_typer(platform_app, name="platform") +app.add_typer(artifacts_app, name="artifacts") +app.add_typer(campaign_app, name="campaign") + + +@app.command("list-decoders") +def list_decoders() -> None: + """Print the registered decoder capability contracts as JSON.""" + _emit_json([describe_decoder(REGISTRY[name]).to_dict() for name in sorted(REGISTRY)]) + + +@dataset_app.command("package") +def dataset_package( + package_name: str = typer.Option(..., "--name", help="Stable logical package name."), + workload: list[str] = typer.Option( + ..., + "--workload", + help="Repeat NAME=JPEG_DIRECTORY for every workload view.", + ), + output_root: Path = typer.Option(..., "--output-root", help="Content-addressed package cache."), + provenance_source: str = typer.Option(..., "--provenance-source"), +) -> None: + """Build one deterministic uncompressed-tar dataset package.""" + workloads = _parse_workloads(workload) + descriptor = build_dataset_package( + package_name=package_name, + workloads=workloads, + output_root=output_root, + provenance={"source": provenance_source}, + ) + _emit_json({"descriptor": str(descriptor), "schema_version": "2.0"}) -def _ensure_venv(group: str, *, force_reinstall: bool = False) -> Path: - """ - Create venvs// and install .[group] into it. - Idempotent: if the venv + project are already there, returns the python path immediately. - """ - if shutil.which("uv") is None: - typer.echo("FATAL: `uv` not found on PATH. Install it: https://astral.sh/uv", err=True) - raise typer.Exit(127) - py = _venv_python(group) - venv_dir = VENV_ROOT / group - needs_install = force_reinstall or not py.exists() +@dataset_app.command("publish") +def dataset_publish( + descriptor: Path = typer.Argument(..., exists=True, dir_okay=False), + store_uri: str = typer.Option(..., "--store", help="GCS base URI, for example gs://bucket/benchmark."), + prefix: str = typer.Option("datasets", "--prefix"), +) -> None: + """Create-only publish every package component to GCS.""" + remote_descriptor = publish_dataset_package( + descriptor, + store=GcloudObjectStore(store_uri), + prefix=prefix, + ) + _emit_json({"remote_descriptor": remote_descriptor, "schema_version": "2.0", "store": store_uri}) - if not py.exists(): - typer.echo(f"[venv] Creating venvs/{group}/ ...") - subprocess.run( - ["uv", "venv", str(venv_dir), "--python", "python3", "--seed"], - check=True, - ) - if needs_install: - typer.echo(f"[venv] Installing .[{group}] into venvs/{group}/ ...") - env = {**os.environ, "UV_LINK_MODE": "copy"} - subprocess.run( - ["uv", "pip", "install", "--python", str(py), "-e", f".[{group}]"], - check=True, - env=env, - ) - else: - typer.echo(f"[venv] Reusing venvs/{group}/") - return py +@dataset_app.command("fodb-package") +def dataset_fodb_package( + archive: list[Path] = typer.Option(..., "--archive", exists=True, dir_okay=False), + output_root: Path = typer.Option(..., "--output-root", file_okay=False), + scene_count: int = typer.Option(12, "--scene-count", min=1), + seed: int = typer.Option(20260729, "--seed"), + compressed_byte_limit: int = typer.Option(2 * 1024**3, "--compressed-byte-limit", min=1), +) -> None: + """Build canonical native/mixed FODB workloads directly from downloaded ZIP parts.""" + descriptor = prepare_fodb( + archive, + output_root, + scene_count=scene_count, + seed=seed, + compressed_byte_limit=compressed_byte_limit, + ) + _emit_json({"descriptor": str(descriptor), "schema_version": "2.0"}) + + +@dataset_app.command("controlled-package") +def dataset_controlled_package( + source_dir: Path = typer.Option(..., "--source-dir", exists=True, file_okay=False), + output_root: Path = typer.Option(..., "--output-root", file_okay=False), + source_name: str = typer.Option(..., "--source-name"), + source_release: str = typer.Option(..., "--source-release"), + source_license: str = typer.Option(..., "--source-license"), + source_url: str | None = typer.Option(None, "--source-url"), + long_edge: list[int] = typer.Option([512, 1024, 2048], "--long-edge", min=1), + quality: list[int] = typer.Option([50, 75, 90, 95], "--quality", min=1, max=95), + include_native: bool = typer.Option(True, "--include-native/--no-native"), + subsampling: str = typer.Option("4:2:0", "--subsampling"), + seed: int = typer.Option(20260729, "--seed"), + compressed_byte_limit: int = typer.Option(2 * 1024**3, "--compressed-byte-limit", min=1), +) -> None: + """Build matched resolution-by-quality workloads from pinned lossless PNGs.""" + from imread_benchmark.datasets.controlled import prepare_controlled_ablation + + descriptor = prepare_controlled_ablation( + source_dir, + output_root, + source_name=source_name, + source_release=source_release, + source_license=source_license, + source_url=source_url, + long_edges=long_edge, + qualities=quality, + include_native=include_native, + subsampling=subsampling, + seed=seed, + compressed_byte_limit=compressed_byte_limit, + ) + _emit_json({"descriptor": str(descriptor), "schema_version": "2.0"}) -# ─── worker invocation ──────────────────────────────────────────────────────── +@dataset_app.command("materialize") +def dataset_materialize( + remote_descriptor: str = typer.Argument(..., help="Object key returned by dataset publish."), + store_uri: str = typer.Option(..., "--store", help="GCS base URI used for publication."), + cache_root: Path = typer.Option(..., "--cache-root"), +) -> None: + """Download, fully verify, and atomically publish a local package cache entry.""" + descriptor = materialize_dataset_package( + remote_descriptor, + store=GcloudObjectStore(store_uri), + cache_root=cache_root, + ) + _emit_json({"descriptor": str(descriptor), "schema_version": "2.0"}) -def _run_worker(py: Path, lib: str, module: str, args: list[str]) -> int: - """Execute a benchmark worker module in a per-group venv subprocess.""" - proc = subprocess.run([str(py), "-m", module, "--library", lib, *args], check=False) - return proc.returncode +@plan_app.command("instantiate") +def plan_instantiate( + template_path: Path = typer.Argument(..., exists=True, dir_okay=False), + package_descriptor: Path = typer.Option(..., "--package-descriptor", exists=True, dir_okay=False), + output_dir: Path = typer.Option(..., "--output-dir", file_okay=False), + workload: list[str] = typer.Option([], "--workload"), +) -> None: + """Fill and validate one plan per selected package workload.""" + plans = instantiate_experiment_plans( + template_path=template_path, + package_descriptor=package_descriptor, + output_dir=output_dir, + workload_ids=tuple(workload), + ) + _emit_json({"plans": [plan.to_dict() for plan in plans], "schema_version": "2.0"}) -def _query_default_threads(py: Path, lib: str) -> int: - """Ask the decoder in its venv how many threads it uses by default.""" - code = f"from imread_benchmark.decoders import REGISTRY; print(REGISTRY['{lib}']().get_num_threads())" - proc = subprocess.run([str(py), "-c", code], check=True, capture_output=True, text=True) - return int(proc.stdout.strip()) +@plan_app.command("validate") +def plan_validate( + plan_path: Path = typer.Argument(..., exists=True, dir_okay=False), + package_descriptor: Path | None = typer.Option(None, "--package-descriptor", dir_okay=False), +) -> None: + """Validate plan semantics and its exact pinned dataset identity.""" + plan = load_experiment_plan(plan_path, dataset_descriptor=package_descriptor) + templates = expand_experiment_plan(plan) + _emit_json( + { + "configuration_count": len({template.configuration.config_id for template in templates}), + "plan_id": templates[0].plan_id, + "run_count_per_platform": len(templates), + "schema_version": "2.0", + }, + ) -def _run_single_for_lib( - py: Path, - lib: str, - *, - data_dir: Path, - output_dir: Path, - num_images: int, - num_runs: int, - mode: str, -) -> bool: - """Run single-thread + (optionally) library-default-thread benchmarks. Returns success.""" - typer.echo(f" [single, 1 thread] {lib}") - rc = _run_worker( - py, - lib, - "imread_benchmark.benchmark_single", - [ - "--data-dir", - str(data_dir), - "--num-images", - str(num_images), - "--num-runs", - str(num_runs), - "--output-dir", - str(output_dir), - "--mode", - mode, - "--num-threads", - "1", - ], - ) - if rc != 0: - typer.echo(f" WARN: 1-thread run failed for {lib} (exit {rc})", err=True) - return False - - default_threads = _query_default_threads(py, lib) - if default_threads <= 1: - typer.echo(" [default = 1 thread, no second pass needed]") - return True - - # If the library ignored set_num_threads(1) (e.g. opencv-on-macOS uses GCD), - # the 1-thread pass actually ran with `effective_threads != 1`. If that - # number already equals what the default would do, the second pass is a - # duplicate — skip it. - one_t_json = output_dir / get_system_identifier() / f"{lib}_1t_results.json" - try: - first_eff = int(json.loads(one_t_json.read_text())["effective_threads"]) - except (FileNotFoundError, KeyError, ValueError, json.JSONDecodeError): - first_eff = 1 # be safe, run the second pass - - if first_eff == default_threads: - typer.echo( - f" [skip default pass] {lib} ignores set_num_threads " - f"(1-thread request → ran with {first_eff}, default also = {default_threads})", - ) - return True - - typer.echo(f" [single, default = {default_threads} threads] {lib}") - rc = _run_worker( - py, - lib, - "imread_benchmark.benchmark_single", - [ - "--data-dir", - str(data_dir), - "--num-images", - str(num_images), - "--num-runs", - str(num_runs), - "--output-dir", - str(output_dir), - "--mode", - mode, - "--num-threads", - "0", +@plan_app.command("expand") +def plan_expand( + plan_path: Path = typer.Argument(..., exists=True, dir_okay=False), + output: Path = typer.Option(..., "--output", dir_okay=False), + package_descriptor: Path | None = typer.Option(None, "--package-descriptor", dir_okay=False), +) -> None: + """Write the deterministic randomized run-template matrix.""" + plan = load_experiment_plan(plan_path, dataset_descriptor=package_descriptor) + templates = expand_experiment_plan(plan) + document = { + "plan_id": templates[0].plan_id, + "runs": [ + { + "block_position": template.position, + "config_id": template.configuration.config_id, + "configuration": asdict(template.configuration), + "repetition": template.repetition, + "template_id": template.template_id, + } + for template in templates ], + "schema_version": "2.0", + } + output.parent.mkdir(parents=True, exist_ok=True) + output.write_text(json.dumps(document, ensure_ascii=False, indent=2, sort_keys=True) + "\n") + _emit_json({"output": str(output.resolve()), "plan_id": templates[0].plan_id, "schema_version": "2.0"}) + + +@environment_app.command("provision") +def environment_provision( + dependency_group: str = typer.Option(..., "--group"), + runner_revision: str = typer.Option(..., "--runner-revision"), + project_root: Path = typer.Option(Path(), "--project-root", file_okay=False), + cache_root: Path = typer.Option(..., "--cache-root", file_okay=False), + python_executable: Path = typer.Option(Path(sys.executable), "--python", dir_okay=False), + native_backend: list[str] = typer.Option([], "--native-backend", help="Repeat NAME=VERSION for native libraries."), + remote_store_uri: str | None = typer.Option(None, "--remote-store"), + remote_prefix: str = typer.Option("environments", "--remote-prefix"), +) -> None: + """Provision or reuse an atomic `uv sync --frozen --no-editable` environment.""" + request = EnvironmentRequest( + project_root=project_root, + cache_root=cache_root, + dependency_group=dependency_group, + runner_revision=runner_revision, + python_executable=python_executable, + native_backends=_parse_name_values(native_backend, option="--native-backend"), ) - if rc != 0: - typer.echo(f" WARN: default-thread run failed for {lib} (exit {rc})", err=True) - return False - return True - - -def _run_dataloader_for_lib( - py: Path, - lib: str, - *, - data_dir: Path, - output_dir: Path, - num_images: int, - num_runs: int, - workers: list[int], -) -> bool: - typer.echo(f" [dataloader] {lib} workers={workers}") - rc = _run_worker( - py, - lib, - "imread_benchmark.benchmark_dataloader", - [ - "--data-dir", - str(data_dir), - "--num-images", - str(num_images), - "--num-runs", - str(num_runs), - "--output-dir", - str(output_dir), - "--workers", - *(str(w) for w in workers), - ], + store = GcloudObjectStore(remote_store_uri) if remote_store_uri is not None else None + result = materialize_environment_cache(request, store=store, prefix=remote_prefix) if store is not None else None + if result is None: + result = provision_environment(request) + if store is not None: + publish_environment_cache(result.root, store=store, prefix=remote_prefix) + _emit_json( + { + "cache_hit": result.cache_hit, + "descriptor": str(result.descriptor_path), + "environment_id": result.environment_id, + "environment_key": result.environment_key, + "python": str(result.python_executable), + "root": str(result.root), + "schema_version": "2.0", + }, ) - if rc != 0: - typer.echo(f" WARN: dataloader run failed for {lib} (exit {rc})", err=True) - return rc == 0 - - -# ─── commands ───────────────────────────────────────────────────────────────── - - -@app.command("list-libs") -def list_libs() -> None: - """List all known decoders and whether they would run on this machine.""" - sys_str, mach_str = platform.system(), platform.machine() - typer.echo(f"Platform: {sys_str} {mach_str}\n") - typer.echo(f"{'name':<14} {'group':<12} {'single':<7} {'dataloader':<10}") - typer.echo("-" * 50) - for name in _all_lib_names(): - cls: type[BaseDecoder] = REGISTRY[name] - s = "yes" if cls.runs_single_here() else "skip" - d = "yes" if cls.runs_dataloader_here() else "skip" - typer.echo(f"{name:<14} {cls.group:<12} {s:<7} {d:<10}") - - -@app.command("run") -def run( - data_dir: Path = typer.Option(..., "--data-dir", "-d", exists=True, help="Directory of JPEG images"), - output_dir: Path = typer.Option(Path("output"), "--output-dir", "-o", help="Where JSON results go"), - libs: str = typer.Option("all", "--libs", help="Comma-separated lib names, or 'all'"), - mode: str = typer.Option("both", "--mode", help="single | dataloader | both"), - num_images: int = typer.Option(50000, "--num-images", "-n"), - num_runs: int = typer.Option(20, "--num-runs", "-r", help="Timed runs for the single-thread benchmark"), - dataloader_runs: int = typer.Option(5, "--dataloader-runs", help="Timed runs per worker count"), - workers: str = typer.Option(DEFAULT_WORKERS, "--workers", help="Comma-separated num_workers values"), - decode_mode: str = typer.Option("memory", "--decode-mode", help="memory | disk (single benchmark only)"), - skip_setup: bool = typer.Option(False, "--skip-setup", help="Assume venvs/ already populated"), -) -> None: - """Benchmark JPEG decoders. Sets up per-group venvs as needed.""" - if mode not in {"single", "dataloader", "both"}: - typer.echo(f"--mode must be single|dataloader|both, got {mode!r}", err=True) - raise typer.Exit(2) - - requested = _resolve_libs(libs) - worker_counts = [int(w) for w in workers.split(",") if w.strip()] - - # Filter by platform predicates per benchmark type. - do_single = mode in {"single", "both"} - do_dl = mode in {"dataloader", "both"} - - single_libs = [name for name in requested if do_single and _runs_single(name)] - dl_libs = [name for name in requested if do_dl and _runs_dataloader(name)] - all_libs = sorted(set(single_libs) | set(dl_libs)) - skipped = sorted(set(requested) - set(all_libs)) - - typer.echo("─" * 60) - typer.echo(f"Data dir : {data_dir}") - typer.echo(f"Output dir : {output_dir}") - typer.echo(f"Mode : {mode}") - typer.echo(f"Images : {num_images}") - typer.echo(f"Runs : single={num_runs} dataloader={dataloader_runs}") - typer.echo(f"Workers : {worker_counts}") - typer.echo(f"Libs (run) : {', '.join(all_libs) or '(none)'}") - if skipped: - typer.echo(f"Skipped : {', '.join(skipped)} (platform-incompatible for chosen mode)") - typer.echo("─" * 60) - - output_dir.mkdir(parents=True, exist_ok=True) - - # Ensure the venv for each group we'll use. - needed_groups = sorted({_group_of(name) for name in all_libs}) - venv_python: dict[str, Path] = {} - for g in needed_groups: - venv_python[g] = _ensure_venv(g, force_reinstall=False) if not skip_setup else _venv_python(g) - - failures: list[str] = [] - - # Iterate libs grouped by their venv to maximise locality (helps log scanning). - for group in needed_groups: - py = venv_python[group] - libs_in_group = [name for name in all_libs if _group_of(name) == group] - - for lib in libs_in_group: - typer.echo(f"\n=== {lib} (group={group}) ===") - if lib in single_libs and not _run_single_for_lib( - py, - lib, - data_dir=data_dir, - output_dir=output_dir, - num_images=num_images, - num_runs=num_runs, - mode=decode_mode, - ): - failures.append(f"{lib}/single") - if lib in dl_libs and not _run_dataloader_for_lib( - py, - lib, - data_dir=data_dir, - output_dir=output_dir, - num_images=num_images, - num_runs=dataloader_runs, - workers=worker_counts, - ): - failures.append(f"{lib}/dataloader") - - typer.echo("\n" + "─" * 60) - if failures: - typer.echo(f"Done with {len(failures)} per-decoder failure(s) — other decoders' results are intact:") - for f in failures: - typer.echo(f" - {f}") - else: - typer.echo("All benchmarks completed successfully.") - - # Persist a machine-readable summary so the cloud orchestrator can tell - # "all decoders finished cleanly" from "9 of 12 finished, 3 failed but - # their JSONs are missing/partial". Without this the only signal is the - # CLI exit code, which we deliberately keep at 0 for partial failures so - # vm_startup.sh writes DONE (not FAILED) and we don't lose the 9 clean - # decoders' work because turbojpeg crashed on a CMYK image. - summary_path = output_dir / get_system_identifier() / "run_summary.json" - summary_path.parent.mkdir(parents=True, exist_ok=True) - summary_payload = { - "timestamp_utc": _dt.datetime.now(_dt.UTC).isoformat(), - "system": get_system_identifier(), - "mode": mode, - "num_images": num_images, - "num_runs": num_runs, - "dataloader_runs": dataloader_runs, - "workers": worker_counts, - "libs_requested": requested, - "libs_skipped_platform": skipped, - "libs_run": all_libs, - "failures": failures, - "exit_status": "ok" if not failures else "partial", - } - with summary_path.open("w") as fh: - json.dump(summary_payload, fh, indent=2) - typer.echo(f"Run summary: {summary_path}") -@app.command("plot") -def plot( - input_dir: Path = typer.Option(Path("output"), "--input", "-i", exists=True), - output_dir: Path = typer.Option(Path("docs/assets/benchmarks"), "--output", "-o"), +@platform_app.command("capture") +def platform_capture( + output: Path = typer.Option(..., "--output", dir_okay=False), + cloud_provider: str = typer.Option("local", "--cloud-provider"), + machine_type: str = typer.Option(..., "--machine-type"), + location: str = typer.Option(..., "--location"), +) -> None: + """Capture stable machine identity and separate dynamic runtime metadata.""" + descriptor = capture_current_platform( + cloud_provider=cloud_provider, + machine_type=machine_type, + location=location, + ) + path = write_platform_descriptor(output, descriptor) + _emit_json({"descriptor": str(path.resolve()), "platform_id": descriptor.platform_id, "schema_version": "2.0"}) + + +@campaign_app.command("run") +def campaign_run( + plan_path: Path = typer.Argument(..., exists=True, dir_okay=False), + package_descriptor: Path = typer.Option(..., "--package-descriptor", exists=True, dir_okay=False), + environment_descriptor: Path = typer.Option(..., "--environment-descriptor", exists=True, dir_okay=False), + platform_descriptor: Path = typer.Option(..., "--platform-descriptor", exists=True, dir_okay=False), + artifact_root: Path = typer.Option(..., "--artifact-root", file_okay=False), + attempts_root: Path = typer.Option(..., "--attempts-root", file_okay=False), + runner_revision: str = typer.Option(..., "--runner-revision"), + remote_store_uri: str | None = typer.Option(None, "--remote-store"), + worker_python: Path = typer.Option(Path(sys.executable), "--worker-python", dir_okay=False), ) -> None: - """Generate paper-quality plots from output/ JSONs. Wraps tools/create_plots.py.""" - import importlib.util - - if any(importlib.util.find_spec(pkg) is None for pkg in ("matplotlib", "seaborn")): - typer.echo( - "Plotting requires matplotlib + seaborn. Install with:\n uv pip install -e '.[plot]'", - err=True, - ) + """Run support audits and missing run specs, checkpointing each bundle.""" + remote = RemoteCheckpoint(GcloudObjectStore(remote_store_uri)) if remote_store_uri is not None else None + result = run_campaign( + CampaignConfig( + plan_path=plan_path, + package_descriptor=package_descriptor, + environment_descriptor=environment_descriptor, + platform_descriptor=platform_descriptor, + artifact_root=artifact_root, + attempts_root=attempts_root, + runner_revision=runner_revision, + worker_python=worker_python, + remote=remote, + ), + ) + _emit_json(result.to_dict()) + if not result.complete: raise typer.Exit(1) - output_dir.mkdir(parents=True, exist_ok=True) - cmd = [sys.executable, "-m", "tools.create_plots", "--input", str(input_dir), "--output", str(output_dir)] - typer.echo("$ " + " ".join(cmd)) - raise typer.Exit(subprocess.run(cmd, check=False).returncode) + +@artifacts_app.command("validate") +def artifacts_validate( + artifact_root: Path = typer.Argument(..., exists=True, file_okay=False), +) -> None: + """Validate every committed run bundle below ARTIFACT_ROOT/runs.""" + run_root = artifact_root / "runs" + bundles = tuple(sorted(path for path in run_root.iterdir() if path.is_dir())) if run_root.is_dir() else () + if not bundles: + raise typer.BadParameter("artifact root contains no run bundles") + for bundle in bundles: + validate_run_bundle(bundle, expected_run_key=bundle.name) + _emit_json({"bundle_count": len(bundles), "schema_version": "2.0", "status": "valid"}) + + +@artifacts_app.command("hydrate") +def artifacts_hydrate( + source_artifact_root: Path = typer.Argument(..., exists=True, file_okay=False), + output_root: Path = typer.Option(..., "--output-root", file_okay=False), +) -> None: + """Rebuild local run bundles from a downloaded remote artifact layout.""" + bundles = hydrate_committed_runs( + source_artifact_root=source_artifact_root, + destination_artifact_root=output_root, + ) + _emit_json( + { + "bundle_count": len(bundles), + "output_root": str(output_root.resolve()), + "schema_version": "2.0", + }, + ) -@app.command("render-readme") -def render_readme( - input_dir: Path = typer.Option(Path("output"), "--input", "-i", exists=True), - readme: Path = typer.Option(Path("README.md"), "--readme", "-r", exists=True), - check: bool = typer.Option(False, "--check", help="Exit 1 if README would change."), +@app.command("publish") +def publication_publish( + spec_path: Path = typer.Argument(..., exists=True, dir_okay=False), + artifact_root: Path = typer.Option(..., "--artifact-root", exists=True, file_okay=False), + output_dir: Path = typer.Option(..., "--output-dir", file_okay=False), + check: bool = typer.Option(False, "--check"), ) -> None: - """Regenerate the BENCH:* tables in README.md from output/ JSONs.""" - cmd = [sys.executable, "-m", "tools.render_readme", "--input", str(input_dir), "--readme", str(readme)] - if check: - cmd.append("--check") - typer.echo("$ " + " ".join(cmd)) - raise typer.Exit(subprocess.run(cmd, check=False).returncode) + """Generate or verify deterministic claim-scoped publication artifacts.""" + publish(artifact_root=artifact_root, spec_path=spec_path, output_dir=output_dir, check=check) + _emit_json({"check": check, "output_dir": str(output_dir.resolve()), "schema_version": "2.0"}) + + +def _parse_workloads(values: list[str]) -> dict[str, Path]: + result: dict[str, Path] = {} + for value in values: + name, separator, raw_path = value.partition("=") + if not separator or not name or not raw_path: + raise typer.BadParameter("each --workload must be NAME=JPEG_DIRECTORY") + if name in result: + raise typer.BadParameter(f"duplicate workload name: {name}") + path = Path(raw_path).resolve() + if not path.is_dir(): + raise typer.BadParameter(f"workload directory does not exist: {path}") + result[name] = path + return result + + +def _parse_name_values(values: list[str], *, option: str) -> tuple[tuple[str, str], ...]: + result: dict[str, str] = {} + for value in values: + name, separator, raw_value = value.partition("=") + if not separator or not name or not raw_value: + raise typer.BadParameter(f"each {option} must be NAME=VALUE") + if name in result: + raise typer.BadParameter(f"duplicate {option} name: {name}") + result[name] = raw_value + return tuple(sorted(result.items())) + + +def _emit_json(payload: object) -> None: + typer.echo(json.dumps(payload, ensure_ascii=False, indent=2, sort_keys=True)) if __name__ == "__main__": diff --git a/imread_benchmark/contracts.py b/imread_benchmark/contracts.py new file mode 100644 index 0000000..8c15d89 --- /dev/null +++ b/imread_benchmark/contracts.py @@ -0,0 +1,48 @@ +from __future__ import annotations + +from dataclasses import dataclass + +import numpy as np + + +class OutputContractError(ValueError): + pass + + +@dataclass(frozen=True, slots=True) +class OutputContract: + kind: str + color_space: str + dtype: str + layout: str + channels: int + + @classmethod + def normalized_rgb(cls) -> OutputContract: + return cls(kind="normalized-rgb", color_space="RGB", dtype="uint8", layout="HWC", channels=3) + + def to_dict(self) -> dict[str, object]: + return { + "channels": self.channels, + "color_space": self.color_space, + "dtype": self.dtype, + "kind": self.kind, + "layout": self.layout, + } + + +def validate_output(output: object, contract: OutputContract) -> None: + if not isinstance(output, np.ndarray): + raise OutputContractError(f"expected numpy.ndarray, got {type(output).__name__}") + if output.dtype.name != contract.dtype: + raise OutputContractError(f"expected dtype {contract.dtype}, got {output.dtype.name}") + if contract.layout != "HWC": + raise OutputContractError(f"unsupported output layout: {contract.layout}") + if output.ndim != 3 or output.shape[-1] != contract.channels: + raise OutputContractError( + f"expected HWC array with {contract.channels} channels, got shape {output.shape}", + ) + if contract.color_space != "RGB": + raise OutputContractError(f"unsupported color space: {contract.color_space}") + if not output.flags.c_contiguous: + raise OutputContractError("normalized output must be C-contiguous and fully materialized") diff --git a/imread_benchmark/datasets/__init__.py b/imread_benchmark/datasets/__init__.py new file mode 100644 index 0000000..d0605e6 --- /dev/null +++ b/imread_benchmark/datasets/__init__.py @@ -0,0 +1,3 @@ +from imread_benchmark.datasets.manifest import DatasetItem, DatasetManifest + +__all__ = ["DatasetItem", "DatasetManifest"] diff --git a/imread_benchmark/datasets/controlled.py b/imread_benchmark/datasets/controlled.py new file mode 100644 index 0000000..29a3681 --- /dev/null +++ b/imread_benchmark/datasets/controlled.py @@ -0,0 +1,406 @@ +from __future__ import annotations + +import hashlib +import shutil +import tempfile +from dataclasses import dataclass +from pathlib import Path +from typing import TYPE_CHECKING + +from PIL import Image, ImageOps, features +from PIL import __version__ as pillow_version + +from imread_benchmark.datasets.package import build_dataset_package + +if TYPE_CHECKING: + from collections.abc import Sequence + +DEFAULT_LONG_EDGES = (512, 1024, 2048) +DEFAULT_QUALITIES = (50, 75, 90, 95) +DEFAULT_COMPRESSED_BYTE_LIMIT = 2 * 1024**3 +DEFAULT_ORDER_SEED = 20260729 + +_SUBSAMPLING_CODES = {"4:4:4": 0, "4:2:2": 1, "4:2:0": 2} +_KNOWN_IMAGE_SUFFIXES = frozenset({".bmp", ".jpeg", ".jpg", ".png", ".tif", ".tiff", ".webp"}) + + +class ControlledDatasetError(ValueError): + pass + + +@dataclass(frozen=True, slots=True) +class _Source: + path: Path + relative_path: str + source_id: str + source_bytes: int + source_sha256: str + width: int + height: int + normalized_rgb_sha256: str + + def to_dict(self) -> dict[str, object]: + return { + "normalized_rgb_sha256": self.normalized_rgb_sha256, + "normalized_rgb_size": [self.width, self.height], + "relative_path": self.relative_path, + "source_bytes": self.source_bytes, + "source_id": self.source_id, + "source_sha256": self.source_sha256, + } + + +@dataclass(frozen=True, slots=True) +class _Design: + long_edges: tuple[int, ...] + qualities: tuple[int, ...] + include_native: bool + subsampling: str + seed: int + compressed_byte_limit: int + + +def prepare_controlled_ablation( # noqa: PLR0913 - every factor is an explicit experimental control + source_dir: str | Path, + output_dir: str | Path, + *, + source_name: str, + source_release: str, + source_license: str, + source_url: str | None = None, + long_edges: Sequence[int] = DEFAULT_LONG_EDGES, + qualities: Sequence[int] = DEFAULT_QUALITIES, + include_native: bool = True, + subsampling: str = "4:2:0", + seed: int = DEFAULT_ORDER_SEED, + compressed_byte_limit: int = DEFAULT_COMPRESSED_BYTE_LIMIT, +) -> Path: + """Build matched JPEG workloads for a controlled resolution-by-quality design.""" + source_root = Path(source_dir).expanduser().resolve() + output_root = Path(output_dir).expanduser().resolve() + source_dataset = _validate_source_dataset( + name=source_name, + release=source_release, + license_name=source_license, + url=source_url, + ) + design = _validate_design( + source_root=source_root, + output_root=output_root, + long_edges=long_edges, + qualities=qualities, + include_native=include_native, + subsampling=subsampling, + seed=seed, + compressed_byte_limit=compressed_byte_limit, + ) + + sources = _read_sources(source_root, minimum_long_edge=max(design.long_edges, default=0)) + if design.include_native: + oversized = next((source for source in sources if max(source.width, source.height) > 65_535), None) + if oversized is not None: + raise ControlledDatasetError( + f"native source exceeds the JPEG dimension limit: {oversized.relative_path} is " + f"{oversized.width}x{oversized.height}", + ) + ordered_sources = tuple( + sorted( + sources, + key=lambda source: ( + hashlib.sha256( + f"controlled-source-order-v2\0{design.seed}\0{source.source_id}".encode(), + ).digest(), + source.source_id, + ), + ), + ) + levels: tuple[int | None, ...] = ( + *design.long_edges, + *((None,) if design.include_native else ()), + ) + workload_ids = { + (long_edge, quality): _workload_id(long_edge, quality) for long_edge in levels for quality in design.qualities + } + + output_root.mkdir(parents=True, exist_ok=True) + staging = Path(tempfile.mkdtemp(prefix=".controlled-ablation.", dir=output_root)) + try: + workload_roots = {key: staging / "workloads" / workload_id for key, workload_id in workload_ids.items()} + totals = _encode_workloads( + ordered_sources=ordered_sources, + levels=levels, + design=design, + workload_ids=workload_ids, + workload_roots=workload_roots, + ) + + ordered_source_ids = [source.source_id for source in ordered_sources] + provenance = { + "dataset": "controlled-resolution-quality-ablation", + "design": { + "controls": { + "chroma_subsampling": design.subsampling, + "color_conversion": "Pillow Image.convert(RGB)", + "encoder_optimize": False, + "encoder_progressive": False, + "encoder_container_headers": "Pillow defaults", + "resize_filter": "Pillow LANCZOS", + "source_metadata": "stripped", + "source_format": "lossless PNG only", + }, + "factors": { + "encoder_quality": list(design.qualities), + "long_edge_pixels": [ + *design.long_edges, + *(("native",) if design.include_native else ()), + ], + }, + "pairing_unit": "source_id", + }, + "encoder": { + "implementation": "Pillow", + "libjpeg_turbo": features.version_feature("libjpeg_turbo"), + "libjpeg_version": features.version("jpg"), + "pillow_version": pillow_version, + }, + "order": {"method": "seeded-sha256-order-v2", "seed": design.seed}, + "ordered_source_ids": ordered_source_ids, + "schema_version": "2.0", + "source_dataset": source_dataset, + "sources": [source.to_dict() for source in sources], + "workloads": { + workload_ids[key]: { + "compressed_byte_limit": design.compressed_byte_limit, + "encoder_quality": key[1], + "item_count": len(ordered_sources), + "long_edge_pixels": key[0] if key[0] is not None else "native", + "ordered_source_ids": ordered_source_ids, + "total_compressed_bytes": totals[key], + } + for key in workload_ids + }, + } + return build_dataset_package( + package_name="controlled-resolution-quality", + workloads={workload_id: workload_roots[key] for key, workload_id in workload_ids.items()}, + output_root=output_root / "packages", + provenance=provenance, + ) + finally: + shutil.rmtree(staging, ignore_errors=True) + + +def _validate_design( # noqa: PLR0913 - mirrors public experimental controls + *, + source_root: Path, + output_root: Path, + long_edges: Sequence[int], + qualities: Sequence[int], + include_native: bool, + subsampling: str, + seed: int, + compressed_byte_limit: int, +) -> _Design: + edges = _validated_unique_positive_ints(long_edges, field="long edge", maximum=65_535) + encoder_qualities = _validated_unique_positive_ints(qualities, field="quality", maximum=95) + if not isinstance(include_native, bool): + raise ControlledDatasetError("include_native must be a boolean") + if not edges and not include_native: + raise ControlledDatasetError("at least one long edge or the native level is required") + if not encoder_qualities: + raise ControlledDatasetError("at least one encoder quality is required") + if subsampling not in _SUBSAMPLING_CODES: + raise ControlledDatasetError(f"subsampling must be one of {sorted(_SUBSAMPLING_CODES)}") + if isinstance(seed, bool) or not isinstance(seed, int): + raise ControlledDatasetError("seed must be an integer") + if ( + isinstance(compressed_byte_limit, bool) + or not isinstance(compressed_byte_limit, int) + or compressed_byte_limit <= 0 + ): + raise ControlledDatasetError("compressed_byte_limit must be a positive integer") + if not source_root.is_dir(): + raise ControlledDatasetError(f"source directory does not exist: {source_root}") + if output_root == source_root or output_root.is_relative_to(source_root): + raise ControlledDatasetError("output directory must not be inside the source directory") + return _Design( + long_edges=edges, + qualities=encoder_qualities, + include_native=include_native, + subsampling=subsampling, + seed=seed, + compressed_byte_limit=compressed_byte_limit, + ) + + +def _validate_source_dataset( + *, + name: str, + release: str, + license_name: str, + url: str | None, +) -> dict[str, str]: + values = {"license": license_name, "name": name, "release": release} + for field, value in values.items(): + if not isinstance(value, str) or not value.strip(): + raise ControlledDatasetError(f"source dataset {field} must be a non-empty string") + normalized = {field: value.strip() for field, value in values.items()} + if url is not None: + if not isinstance(url, str) or not url.strip(): + raise ControlledDatasetError("source dataset URL must be a non-empty string when provided") + normalized["url"] = url.strip() + return normalized + + +def _encode_workloads( + *, + ordered_sources: tuple[_Source, ...], + levels: tuple[int | None, ...], + design: _Design, + workload_ids: dict[tuple[int | None, int], str], + workload_roots: dict[tuple[int | None, int], Path], +) -> dict[tuple[int | None, int], int]: + totals = dict.fromkeys(workload_ids, 0) + for order_index, source in enumerate(ordered_sources): + with _open_normalized_rgb(source.path) as original: + for long_edge in levels: + rendered = original.copy() if long_edge is None else _resize_long_edge(original, long_edge) + try: + for quality in design.qualities: + key = (long_edge, quality) + destination = workload_roots[key] / f"order-{order_index:06d}" / f"{source.source_id}.jpg" + destination.parent.mkdir(parents=True, exist_ok=True) + rendered.save( + destination, + format="JPEG", + quality=quality, + subsampling=_SUBSAMPLING_CODES[design.subsampling], + optimize=False, + progressive=False, + exif=b"", + ) + totals[key] += destination.stat().st_size + _validate_workload_budget( + workload_id=workload_ids[key], + resident_bytes=totals[key], + limit=design.compressed_byte_limit, + ) + finally: + rendered.close() + return totals + + +def _validate_workload_budget(*, workload_id: str, resident_bytes: int, limit: int) -> None: + if resident_bytes > limit: + raise ControlledDatasetError( + f"{workload_id} exceeds the resident compressed-byte budget: " + f"{resident_bytes} bytes exceed the {limit}-byte budget", + ) + + +def _read_sources(root: Path, *, minimum_long_edge: int) -> tuple[_Source, ...]: + image_paths = tuple( + sorted( + (path for path in root.rglob("*") if path.is_file() and path.suffix.lower() in _KNOWN_IMAGE_SUFFIXES), + ), + ) + non_png = tuple(path for path in image_paths if path.suffix.lower() != ".png") + if non_png: + relative = non_png[0].relative_to(root).as_posix() + raise ControlledDatasetError(f"controlled sources must be lossless PNG files; found {relative}") + if not image_paths: + raise ControlledDatasetError(f"no lossless PNG source images found under {root}") + + sources: list[_Source] = [] + seen_source_ids: set[str] = set() + for path in image_paths: + relative_path = path.relative_to(root).as_posix() + source_sha256 = _sha256_file(path) + with _open_normalized_rgb(path) as image: + width, height = image.size + if max(width, height) < minimum_long_edge: + raise ControlledDatasetError( + f"source {relative_path} is smaller than requested long edge {minimum_long_edge}: {width}x{height}", + ) + normalized_digest = hashlib.sha256() + normalized_digest.update(f"RGB\0{width}\0{height}\0".encode()) + normalized_digest.update(image.tobytes()) + source_id = hashlib.sha256(f"{relative_path}\0{source_sha256}".encode()).hexdigest() + if source_id in seen_source_ids: + raise ControlledDatasetError(f"duplicate controlled source identity: {relative_path}") + seen_source_ids.add(source_id) + sources.append( + _Source( + path=path, + relative_path=relative_path, + source_id=source_id, + source_bytes=path.stat().st_size, + source_sha256=source_sha256, + width=width, + height=height, + normalized_rgb_sha256=normalized_digest.hexdigest(), + ), + ) + return tuple(sources) + + +def _open_normalized_rgb(path: Path) -> Image.Image: + try: + encoded = Image.open(path) + except (OSError, ValueError) as exc: + raise ControlledDatasetError(f"cannot decode controlled source {path}: {exc}") from exc + with encoded: + _validate_source_image(encoded, path) + normalized = ImageOps.exif_transpose(encoded).convert("RGB") + normalized.load() + normalized.info.clear() + return normalized + + +def _validate_source_image(image: Image.Image, path: Path) -> None: + if image.format != "PNG": + raise ControlledDatasetError(f"controlled source is not a PNG: {path}") + if getattr(image, "n_frames", 1) != 1: + raise ControlledDatasetError(f"animated or multi-frame PNG is not supported: {path}") + if "A" in image.getbands() or "transparency" in image.info: + raise ControlledDatasetError(f"transparent PNG requires an undeclared compositing policy: {path}") + + +def _resize_long_edge(image: Image.Image, long_edge: int) -> Image.Image: + width, height = image.size + if width >= height: + target = (long_edge, max(1, (height * long_edge + width // 2) // width)) + else: + target = (max(1, (width * long_edge + height // 2) // height), long_edge) + return image.resize(target, resample=Image.Resampling.LANCZOS) + + +def _validated_unique_positive_ints( + values: Sequence[int], + *, + field: str, + maximum: int | None = None, +) -> tuple[int, ...]: + result: list[int] = [] + for value in values: + if isinstance(value, bool) or not isinstance(value, int) or value <= 0: + raise ControlledDatasetError(f"each {field} must be a positive integer") + if maximum is not None and value > maximum: + raise ControlledDatasetError(f"each {field} must be at most {maximum}") + if value in result: + raise ControlledDatasetError(f"duplicate {field}: {value}") + result.append(value) + return tuple(sorted(result)) + + +def _workload_id(long_edge: int | None, quality: int) -> str: + size = "native" if long_edge is None else f"le{long_edge:04d}" + return f"controlled-{size}-q{quality:03d}" + + +def _sha256_file(path: Path) -> str: + digest = hashlib.sha256() + with path.open("rb") as file: + while chunk := file.read(8 * 1024 * 1024): + digest.update(chunk) + return digest.hexdigest() diff --git a/imread_benchmark/datasets/fodb.py b/imread_benchmark/datasets/fodb.py new file mode 100644 index 0000000..a7ed479 --- /dev/null +++ b/imread_benchmark/datasets/fodb.py @@ -0,0 +1,635 @@ +from __future__ import annotations + +import hashlib +import json +import os +import re +import shutil +import zlib +from collections import defaultdict +from dataclasses import dataclass +from pathlib import Path +from typing import TYPE_CHECKING +from zipfile import ZipFile, ZipInfo + +from imread_benchmark.datasets.manifest import DatasetManifest +from imread_benchmark.datasets.package import build_dataset_package + +if TYPE_CHECKING: + from collections.abc import Iterable, Sequence + +FODB_PROVENANCES = ("orig", "facebook", "instagram", "telegram", "twitter", "whatsapp") +FODB_DEVICE_COUNT = 27 +DEFAULT_SCENE_COUNT = 12 +DEFAULT_SELECTION_SEED = 20260729 +DEFAULT_COMPRESSED_BYTE_LIMIT = 2 * 1024**3 +QUALITY_ESTIMATOR = "ijg-luma-sum-v2" + +_MEMBER_RE = re.compile( + r"^(?PD(?P\d{2})_[^/]+)/" + r"(?Porig|facebook|instagram|telegram|twitter|whatsapp)/" + r"D(?P=device_number)_img_(?P=provenance)_(?P\d{4})\.jpg$", +) +_SOF_MARKERS = frozenset( + { + 0xC0, + 0xC1, + 0xC2, + 0xC3, + 0xC5, + 0xC6, + 0xC7, + 0xC9, + 0xCA, + 0xCB, + 0xCD, + 0xCE, + 0xCF, + }, +) +_IJG_LUMA_Q50 = ( + 16, + 11, + 10, + 16, + 24, + 40, + 51, + 61, + 12, + 12, + 14, + 19, + 26, + 58, + 60, + 55, + 14, + 13, + 16, + 24, + 40, + 57, + 69, + 56, + 14, + 17, + 22, + 29, + 51, + 87, + 80, + 62, + 18, + 22, + 37, + 56, + 68, + 109, + 103, + 77, + 24, + 35, + 55, + 64, + 81, + 104, + 113, + 92, + 49, + 64, + 78, + 87, + 103, + 121, + 120, + 101, + 72, + 92, + 95, + 98, + 112, + 100, + 103, + 99, +) + + +@dataclass(frozen=True, slots=True) +class FodbMember: + archive_index: int + archive_id: str + archive_path: Path + zip_member: str + device_id: str + device_dir: str + provenance: str + scene_id: str + jpeg_bytes: int + zip_compressed_bytes: int + crc32: str + + +@dataclass(frozen=True, slots=True) +class JpegDescriptor: + width: int | None + height: int | None + components: int | None + progressive: bool | None + scan_count: int + subsampling: str | None + sampling_factors: tuple[tuple[int, int, int], ...] + quantization_tables: tuple[tuple[int, tuple[int, ...]], ...] + quantization_table_sha256: str | None + quality_estimate: int | None + quality_estimator: str + exif_bytes: int + icc_bytes: int + other_metadata_bytes: int + parse_error: str | None + + def to_dict(self) -> dict[str, object]: + megapixels = None + if self.width is not None and self.height is not None: + megapixels = self.width * self.height / 1_000_000 + return { + "components": self.components, + "exif_bytes": self.exif_bytes, + "height": self.height, + "icc_bytes": self.icc_bytes, + "megapixels": megapixels, + "other_metadata_bytes": self.other_metadata_bytes, + "parse_error": self.parse_error, + "progressive": self.progressive, + "quality_estimate": self.quality_estimate, + "quality_estimator": self.quality_estimator, + "quantization_table_sha256": self.quantization_table_sha256, + "quantization_tables": {str(table_id): list(values) for table_id, values in self.quantization_tables}, + "sampling_factors": [list(factors) for factors in self.sampling_factors], + "scan_count": self.scan_count, + "subsampling": self.subsampling, + "width": self.width, + } + + +def read_fodb_catalog(archive_paths: Sequence[str | Path]) -> tuple[FodbMember, ...]: + if not archive_paths: + raise ValueError("at least one FODB archive is required") + + members: list[FodbMember] = [] + seen_keys: set[tuple[str, str, str]] = set() + for archive_index, raw_path in enumerate(archive_paths, start=1): + archive_path = Path(raw_path).expanduser().resolve() + archive_id = f"part{archive_index:02d}.zip" + with ZipFile(archive_path) as archive: + for info in archive.infolist(): + member = _parse_member(info, archive_index, archive_id, archive_path) + if member is None: + continue + key = (member.device_id, member.provenance, member.scene_id) + if key in seen_keys: + raise ValueError(f"duplicate FODB member for {key}: {member.zip_member}") + seen_keys.add(key) + members.append(member) + + if not members: + raise ValueError("no FODB JPEG members found") + provenance_order = {value: index for index, value in enumerate(FODB_PROVENANCES)} + return tuple( + sorted( + members, + key=lambda item: ( + item.scene_id, + item.device_id, + provenance_order[item.provenance], + ), + ), + ) + + +def complete_scene_ids( + members: Sequence[FodbMember], + *, + expected_device_count: int = FODB_DEVICE_COUNT, + provenances: Sequence[str] = FODB_PROVENANCES, +) -> tuple[str, ...]: + device_ids = sorted({member.device_id for member in members}) + if len(device_ids) != expected_device_count: + raise ValueError(f"expected {expected_device_count} FODB devices, found {len(device_ids)}") + + expected = {(device_id, provenance) for device_id in device_ids for provenance in provenances} + observed_by_scene: dict[str, set[tuple[str, str]]] = defaultdict(set) + for member in members: + if member.provenance in provenances: + observed_by_scene[member.scene_id].add((member.device_id, member.provenance)) + return tuple(sorted(scene_id for scene_id, observed in observed_by_scene.items() if observed == expected)) + + +def select_scene_ids(scene_ids: Sequence[str], *, count: int, seed: int) -> tuple[str, ...]: + if count <= 0: + raise ValueError("scene count must be positive") + ranked = sorted( + set(scene_ids), + key=lambda scene_id: ( + hashlib.sha256(f"fodb-scene-v2\0{seed}\0{scene_id}".encode()).digest(), + scene_id, + ), + ) + return tuple(ranked[:count]) + + +def prepare_fodb( # noqa: PLR0913 - these are the predeclared dataset protocol controls + archive_paths: Sequence[str | Path], + output_dir: str | Path, + *, + scene_count: int = DEFAULT_SCENE_COUNT, + seed: int = DEFAULT_SELECTION_SEED, + compressed_byte_limit: int = DEFAULT_COMPRESSED_BYTE_LIMIT, + expected_device_count: int = FODB_DEVICE_COUNT, + hash_archives: bool = True, +) -> Path: + resolved_archives = tuple(Path(path).expanduser().resolve() for path in archive_paths) + output_root = Path(output_dir).expanduser().resolve() + members = read_fodb_catalog(resolved_archives) + complete = complete_scene_ids(members, expected_device_count=expected_device_count) + selected_scenes = select_scene_ids(complete, count=min(scene_count, len(complete)), seed=seed) + if not selected_scenes: + raise ValueError("FODB contains no complete scenes for the requested device/provenance matrix") + + selected = tuple(member for member in members if member.scene_id in selected_scenes) + expected_items = len(selected_scenes) * expected_device_count * len(FODB_PROVENANCES) + if len(selected) != expected_items: + raise ValueError(f"selected matrix should contain {expected_items} JPEGs, found {len(selected)}") + selection_identity = { + "expected_device_count": expected_device_count, + "method": "seeded-sha256-order-v2", + "provenances": list(FODB_PROVENANCES), + "schema_version": "2.0", + "seed": seed, + "selected_scene_ids": list(selected_scenes), + } + selection_id = _digest_json(selection_identity) + selection_root = output_root / "selections" / selection_id + + workloads = { + "FODB-native": tuple(member for member in selected if member.provenance == "orig"), + "FODB-mixed": selected, + } + workload_payload: dict[str, object] = {} + for workload_name, workload_members in workloads.items(): + resident_bytes = sum(member.jpeg_bytes for member in workload_members) + if resident_bytes > compressed_byte_limit: + raise ValueError( + f"{workload_name} exceeds the resident compressed-byte budget: {resident_bytes} bytes exceed the " + f"{compressed_byte_limit}-byte budget; reduce --scene-count or raise the budget explicitly", + ) + + corpus_root = output_root / "corpus" + descriptors = _extract_and_describe(selected, corpus_root) + for workload_name, workload_members in workloads.items(): + workload_payload[workload_name] = _materialize_workload( + workload_name, + workload_members, + corpus_root=corpus_root, + output_root=selection_root / "workloads" / workload_name.lower(), + selection_seed=seed, + compressed_byte_limit=compressed_byte_limit, + ) + + descriptor_by_member = {member_name: descriptor for member_name, descriptor, _, _ in descriptors} + item_payload = [] + for member in selected: + descriptor, sha256, crc32 = descriptor_by_member[member.zip_member] + width, height = descriptor.width, descriptor.height + bits_per_pixel = None + if width is not None and height is not None and width > 0 and height > 0: + bits_per_pixel = member.jpeg_bytes * 8 / (width * height) + item_payload.append( + { + "archive_id": member.archive_id, + "archive_member_crc32": member.crc32, + "bits_per_pixel": bits_per_pixel, + "crc32": crc32, + "device_dir": member.device_dir, + "device_id": member.device_id, + "jpeg_bytes": member.jpeg_bytes, + "jpeg_sha256": sha256, + "jpeg": descriptor.to_dict(), + "provenance": member.provenance, + "scene_id": member.scene_id, + "zip_compressed_bytes": member.zip_compressed_bytes, + "zip_member": member.zip_member, + }, + ) + + archive_payload = [] + for index, archive_path in enumerate(resolved_archives, start=1): + archive_payload.append( + { + "archive_id": f"part{index:02d}.zip", + "bytes": archive_path.stat().st_size, + "sha256": _sha256_file(archive_path) if hash_archives else None, + "source_basename": archive_path.name, + }, + ) + + payload = { + "archives": archive_payload, + "complete_scene_count": len(complete), + "dataset": "Forchheim Image Database (FODB)", + "device_ids": sorted({member.device_id for member in members}), + "items": item_payload, + "license_note": "Research use; do not redistribute selected JPEG files.", + "provenances": list(FODB_PROVENANCES), + "schema_version": "2.0", + "selection": { + "available_complete_scene_ids": list(complete), + "requested_scene_count": scene_count, + "selection_id": selection_id, + **selection_identity, + }, + "source_url": "https://faui1-files.cs.fau.de/public/mmsec/datasets/fodb/", + "workloads": workload_payload, + } + return build_dataset_package( + package_name="fodb-selected", + workloads={ + "fodb-native": selection_root / "workloads" / "fodb-native", + "fodb-mixed": selection_root / "workloads" / "fodb-mixed", + }, + output_root=output_root / "packages", + provenance=payload, + ) + + +def _parse_member( + info: ZipInfo, + archive_index: int, + archive_id: str, + archive_path: Path, +) -> FodbMember | None: + if info.is_dir(): + return None + match = _MEMBER_RE.fullmatch(info.filename) + if match is None: + return None + device_id = f"D{match.group('device_number')}" + return FodbMember( + archive_index=archive_index, + archive_id=archive_id, + archive_path=archive_path, + zip_member=info.filename, + device_id=device_id, + device_dir=match.group("device_dir"), + provenance=match.group("provenance"), + scene_id=match.group("scene_id"), + jpeg_bytes=info.file_size, + zip_compressed_bytes=info.compress_size, + crc32=f"{info.CRC:08x}", + ) + + +def _extract_and_describe( + members: Sequence[FodbMember], + corpus_root: Path, +) -> tuple[tuple[str, tuple[JpegDescriptor, str, str], str, str], ...]: + by_archive: dict[Path, list[FodbMember]] = defaultdict(list) + for member in members: + by_archive[member.archive_path].append(member) + + for archive_path, archive_members in by_archive.items(): + with ZipFile(archive_path) as archive: + for member in archive_members: + _extract_one(archive, member, corpus_root / member.zip_member) + + result = [] + for member in members: + path = corpus_root / member.zip_member + data = path.read_bytes() + crc32 = f"{zlib.crc32(data) & 0xFFFFFFFF:08x}" + if len(data) != member.jpeg_bytes or crc32 != member.crc32: + raise ValueError(f"extracted JPEG differs from ZIP metadata: {member.zip_member}") + result.append( + ( + member.zip_member, + (_describe_jpeg(data), hashlib.sha256(data).hexdigest(), crc32), + member.device_id, + member.scene_id, + ), + ) + return tuple(result) + + +def _extract_one(archive: ZipFile, member: FodbMember, destination: Path) -> None: + if destination.exists(): + if destination.stat().st_size != member.jpeg_bytes: + raise FileExistsError(f"existing extracted file has unexpected size: {destination}") + return + + destination.parent.mkdir(parents=True, exist_ok=True) + temporary = destination.with_name(f".{destination.name}.partial") + if temporary.exists(): + temporary.unlink() + try: + with archive.open(member.zip_member) as source, temporary.open("xb") as target: + shutil.copyfileobj(source, target, length=1024 * 1024) + if temporary.stat().st_size != member.jpeg_bytes: + raise ValueError(f"truncated extraction for {member.zip_member}") + temporary.replace(destination) + finally: + temporary.unlink(missing_ok=True) + + +def _materialize_workload( # noqa: PLR0913 - explicit output roots keep artifact placement auditable + workload_name: str, + members: Sequence[FodbMember], + *, + corpus_root: Path, + output_root: Path, + selection_seed: int, + compressed_byte_limit: int, +) -> dict[str, object]: + ordered_members = sorted( + members, + key=lambda member: hashlib.sha256( + f"fodb-item-v2\0{selection_seed}\0{workload_name}\0{member.zip_member}".encode(), + ).digest(), + ) + for order_index, member in enumerate(ordered_members): + source = corpus_root / member.zip_member + destination = output_root / f"order-{order_index:04d}" / member.zip_member + destination.parent.mkdir(parents=True, exist_ok=True) + if destination.exists(): + if not destination.samefile(source): + raise FileExistsError(f"existing workload file is not the expected hard link: {destination}") + else: + os.link(source, destination) + + manifest = DatasetManifest.build(output_root, dataset_name=workload_name) + max_decoded_bytes = max((item.width or 0) * (item.height or 0) * 3 for item in manifest.items) + + return { + "compressed_byte_limit": compressed_byte_limit, + "max_decoded_rgb_bytes": max_decoded_bytes, + "num_items": len(members), + "provenances": sorted({member.provenance for member in members}, key=FODB_PROVENANCES.index), + "scene_ids": sorted({member.scene_id for member in members}), + "total_compressed_bytes": sum(member.jpeg_bytes for member in members), + "traversal_order": "seeded-sha256-order-v2", + } + + +def _describe_jpeg(data: bytes) -> JpegDescriptor: + width = height = components = None + progressive = None + sampling_factors: tuple[tuple[int, int, int], ...] = () + quantization_tables: dict[int, tuple[int, ...]] = {} + scan_count = exif_bytes = icc_bytes = other_metadata_bytes = 0 + parse_error = None + try: + for marker, payload in _iter_jpeg_segments(data): + if marker == 0xDA: + scan_count += 1 + elif marker in _SOF_MARKERS and width is None: + height = int.from_bytes(payload[1:3], "big") + width = int.from_bytes(payload[3:5], "big") + components = payload[5] + progressive = marker == 0xC2 + sampling_factors = _sampling_factors(payload, components) + elif marker == 0xDB: + quantization_tables.update(_parse_dqt(payload)) + elif marker == 0xE1 and payload.startswith(b"Exif\x00\x00"): + exif_bytes += len(payload) + elif marker == 0xE2 and payload.startswith(b"ICC_PROFILE\x00"): + icc_bytes += len(payload) + elif 0xE0 <= marker <= 0xEF or marker == 0xFE: + other_metadata_bytes += len(payload) + except (IndexError, ValueError) as exc: + parse_error = str(exc) + + table_items = tuple(sorted(quantization_tables.items())) + table_hash = None + if table_items: + canonical = json.dumps({str(key): list(value) for key, value in table_items}, separators=(",", ":")) + table_hash = hashlib.sha256(canonical.encode()).hexdigest() + return JpegDescriptor( + width=width, + height=height, + components=components, + progressive=progressive, + scan_count=scan_count, + subsampling=_subsampling_label(sampling_factors, components), + sampling_factors=sampling_factors, + quantization_tables=table_items, + quantization_table_sha256=table_hash, + quality_estimate=_estimate_quality(quantization_tables.get(0)), + quality_estimator=QUALITY_ESTIMATOR, + exif_bytes=exif_bytes, + icc_bytes=icc_bytes, + other_metadata_bytes=other_metadata_bytes, + parse_error=parse_error, + ) + + +def _iter_jpeg_segments(data: bytes) -> Iterable[tuple[int, bytes]]: + if not data.startswith(b"\xff\xd8"): + raise ValueError("missing JPEG SOI marker") + position = 2 + while position < len(data): + marker_start = data.find(b"\xff", position) + if marker_start < 0: + return + position = marker_start + 1 + while position < len(data) and data[position] == 0xFF: + position += 1 + if position >= len(data): + return + marker = data[position] + position += 1 + if marker == 0xD9: + return + if marker in {0x00, 0x01} or 0xD0 <= marker <= 0xD8: + continue + if position + 2 > len(data): + raise ValueError("truncated JPEG segment length") + segment_length = int.from_bytes(data[position : position + 2], "big") + if segment_length < 2 or position + segment_length > len(data): + raise ValueError("invalid JPEG segment length") + payload = data[position + 2 : position + segment_length] + yield marker, payload + position += segment_length + + +def _sampling_factors(payload: bytes, components: int) -> tuple[tuple[int, int, int], ...]: + if len(payload) < 6 + 3 * components: + raise ValueError("truncated JPEG start-of-frame segment") + result = [] + for component_index in range(components): + offset = 6 + 3 * component_index + component_id = payload[offset] + sampling = payload[offset + 1] + result.append((component_id, sampling >> 4, sampling & 0x0F)) + return tuple(result) + + +def _subsampling_label( + sampling_factors: Sequence[tuple[int, int, int]], + components: int | None, +) -> str | None: + if components == 1: + return "grayscale" + if components != 3 or not sampling_factors: + return None + horizontal, vertical = sampling_factors[0][1:] + if (horizontal, vertical) == (1, 1): + return "4:4:4" + if (horizontal, vertical) == (2, 1): + return "4:2:2" + if (horizontal, vertical) == (2, 2): + return "4:2:0" + return "other" + + +def _parse_dqt(payload: bytes) -> dict[int, tuple[int, ...]]: + tables: dict[int, tuple[int, ...]] = {} + position = 0 + while position < len(payload): + header = payload[position] + position += 1 + precision, table_id = header >> 4, header & 0x0F + value_bytes = 2 if precision else 1 + end = position + 64 * value_bytes + if precision not in {0, 1} or end > len(payload): + raise ValueError("invalid JPEG quantization table") + values = tuple( + int.from_bytes(payload[offset : offset + value_bytes], "big") + for offset in range(position, end, value_bytes) + ) + tables[table_id] = values + position = end + return tables + + +def _estimate_quality(luma_table: Sequence[int] | None) -> int | None: + if luma_table is None or len(luma_table) != 64: + return None + observed_sum = sum(luma_table) + return min(range(1, 101), key=lambda quality: abs(_ijg_luma_sum(quality) - observed_sum)) + + +def _ijg_luma_sum(quality: int) -> int: + scale = 5000 // quality if quality < 50 else 200 - 2 * quality + return sum(min(255, max(1, (value * scale + 50) // 100)) for value in _IJG_LUMA_Q50) + + +def _sha256_file(path: Path) -> str: + digest = hashlib.sha256() + with path.open("rb") as file: + while chunk := file.read(8 * 1024 * 1024): + digest.update(chunk) + return digest.hexdigest() + + +def _digest_json(payload: object) -> str: + canonical = json.dumps(payload, ensure_ascii=False, separators=(",", ":"), sort_keys=True) + return hashlib.sha256(canonical.encode()).hexdigest() diff --git a/imread_benchmark/datasets/gcs.py b/imread_benchmark/datasets/gcs.py new file mode 100644 index 0000000..22b0b8d --- /dev/null +++ b/imread_benchmark/datasets/gcs.py @@ -0,0 +1,119 @@ +from __future__ import annotations + +import hashlib +import json +import os +import shutil +import subprocess +import tempfile +from dataclasses import dataclass +from pathlib import Path +from typing import TYPE_CHECKING + +from imread_benchmark.datasets.materializer import ( + ObjectConflictError, + ObjectMetadata, + ObjectNotFoundError, + ObjectStoreError, +) + +if TYPE_CHECKING: + from collections.abc import Mapping + + +@dataclass(frozen=True, slots=True) +class GcloudObjectStore: + base_uri: str + command_prefix: tuple[str, ...] + environment: dict[str, str] + + def __init__( + self, + base_uri: str, + *, + command_prefix: tuple[str, ...] | None = None, + environment: Mapping[str, str] | None = None, + ) -> None: + normalized = base_uri.rstrip("/") + if not normalized.startswith("gs://") or normalized.count("/") < 2: + raise ValueError("base_uri must be a gs:// bucket URI") + if command_prefix is None: + executable = shutil.which("gcloud") + if executable is None: + raise ObjectStoreError("gcloud executable is not available") + command_prefix = (executable,) + if not command_prefix: + raise ValueError("command_prefix must not be empty") + object.__setattr__(self, "base_uri", normalized) + object.__setattr__(self, "command_prefix", command_prefix) + object.__setattr__(self, "environment", {**os.environ, **(environment or {})}) + + def put_create_only(self, source: Path, key: str) -> None: + uri = self._uri(key) + result = self._run(("storage", "cp", str(source), uri, "--if-generation-match=0", "--quiet")) + if result.returncode != 0: + with tempfile.TemporaryDirectory(prefix="gcs-existing.") as directory: + existing = Path(directory) / source.name + try: + self.download(key, existing) + except ObjectStoreError as exc: + raise ObjectStoreError(f"create-only upload failed for {uri}: {result.stderr.strip()}") from exc + if _sha256_file(existing) != _sha256_file(source): + raise ObjectConflictError(f"remote object already exists with different content: {uri}") + if self.metadata(key).size != source.stat().st_size: + raise ObjectStoreError(f"remote object size mismatch after upload: {uri}") + + def download(self, key: str, destination: Path) -> None: + self.metadata(key) + destination.parent.mkdir(parents=True, exist_ok=True) + descriptor, temporary_name = tempfile.mkstemp( + prefix=f".{destination.name}.", + dir=destination.parent, + ) + os.close(descriptor) + temporary = Path(temporary_name) + try: + result = self._run(("storage", "cp", self._uri(key), str(temporary), "--quiet")) + if result.returncode != 0: + raise ObjectStoreError(f"download failed for {self._uri(key)}: {result.stderr.strip()}") + temporary.replace(destination) + finally: + temporary.unlink(missing_ok=True) + + def metadata(self, key: str) -> ObjectMetadata: + uri = self._uri(key) + result = self._run(("storage", "objects", "describe", uri, "--format=json")) + if result.returncode != 0: + stderr = result.stderr.lower() + if "not found" in stderr or "404" in stderr or "matched no objects" in stderr: + raise ObjectNotFoundError(f"remote object does not exist: {uri}") + raise ObjectStoreError(f"cannot inspect remote object {uri}: {result.stderr.strip()}") + try: + document = json.loads(result.stdout) + size = int(document["size"]) + generation = str(document["generation"]) + except (KeyError, TypeError, ValueError, json.JSONDecodeError) as exc: + raise ObjectStoreError(f"invalid gcloud metadata response for {uri}") from exc + return ObjectMetadata(size=size, generation=generation) + + def _uri(self, key: str) -> str: + if not key or key.startswith("/") or ".." in Path(key).parts: + raise ValueError(f"unsafe object key: {key!r}") + return f"{self.base_uri}/{key}" + + def _run(self, arguments: tuple[str, ...]) -> subprocess.CompletedProcess[str]: + return subprocess.run( # noqa: S603 - executable is resolved or explicitly injected; argv is not shell text + (*self.command_prefix, *arguments), + check=False, + capture_output=True, + env=self.environment, + text=True, + ) + + +def _sha256_file(path: Path) -> str: + digest = hashlib.sha256() + with path.open("rb") as file: + while chunk := file.read(1024 * 1024): + digest.update(chunk) + return digest.hexdigest() diff --git a/imread_benchmark/datasets/manifest.py b/imread_benchmark/datasets/manifest.py new file mode 100644 index 0000000..3058db5 --- /dev/null +++ b/imread_benchmark/datasets/manifest.py @@ -0,0 +1,221 @@ +from __future__ import annotations + +import hashlib +import json +from dataclasses import dataclass +from pathlib import Path +from typing import TYPE_CHECKING + +MANIFEST_SCHEMA_VERSION = "2.0" + +if TYPE_CHECKING: + from collections.abc import Iterator + +_START_OF_FRAME_MARKERS = frozenset( + { + 0xC0, + 0xC1, + 0xC2, + 0xC3, + 0xC5, + 0xC6, + 0xC7, + 0xC9, + 0xCA, + 0xCB, + 0xCD, + 0xCE, + 0xCF, + }, +) + + +@dataclass(frozen=True, slots=True) +class _JpegProperties: + width: int | None = None + height: int | None = None + components: int | None = None + progressive: bool | None = None + subsampling: str | None = None + has_exif: bool = False + parse_error: str | None = None + + +@dataclass(frozen=True, slots=True) +class DatasetItem: + item_id: str + relative_path: str + sha256: str + compressed_bytes: int + width: int | None + height: int | None + components: int | None + progressive: bool | None + subsampling: str | None + has_exif: bool + parse_error: str | None = None + + def to_dict(self) -> dict[str, object]: + return { + "components": self.components, + "compressed_bytes": self.compressed_bytes, + "has_exif": self.has_exif, + "height": self.height, + "item_id": self.item_id, + "parse_error": self.parse_error, + "progressive": self.progressive, + "relative_path": self.relative_path, + "sha256": self.sha256, + "subsampling": self.subsampling, + "width": self.width, + } + + +@dataclass(frozen=True, slots=True) +class DatasetManifest: + root: Path + dataset_name: str + manifest_id: str + items: tuple[DatasetItem, ...] + schema_version: str = MANIFEST_SCHEMA_VERSION + + @classmethod + def build( + cls, + root: str | Path, + *, + dataset_name: str, + ) -> DatasetManifest: + resolved_root = Path(root).resolve() + paths = sorted( + path for path in resolved_root.rglob("*") if path.is_file() and path.suffix.lower() in {".jpg", ".jpeg"} + ) + if not paths: + raise ValueError(f"no JPEG files found under {resolved_root}") + + items = tuple(_build_item(resolved_root, path) for path in paths) + manifest_id = _manifest_digest(dataset_name, items) + return cls( + root=resolved_root, + dataset_name=dataset_name, + manifest_id=manifest_id, + items=items, + ) + + def resolve(self, item: DatasetItem) -> Path: + return self.root / item.relative_path + + def to_dict(self) -> dict[str, object]: + return { + "dataset_name": self.dataset_name, + "items": [item.to_dict() for item in self.items], + "manifest_id": self.manifest_id, + "schema_version": self.schema_version, + } + + +def _build_item(root: Path, path: Path) -> DatasetItem: + data = path.read_bytes() + digest = hashlib.sha256(data).hexdigest() + relative_path = path.relative_to(root).as_posix() + item_id = hashlib.sha256(f"{relative_path}\0{digest}".encode()).hexdigest() + properties = _parse_jpeg_properties(data) + return DatasetItem( + item_id=item_id, + relative_path=relative_path, + sha256=digest, + compressed_bytes=len(data), + width=properties.width, + height=properties.height, + components=properties.components, + progressive=properties.progressive, + subsampling=properties.subsampling, + has_exif=properties.has_exif, + parse_error=properties.parse_error, + ) + + +def _manifest_digest(dataset_name: str, items: tuple[DatasetItem, ...]) -> str: + payload = { + "dataset_name": dataset_name, + "items": [item.to_dict() for item in items], + "schema_version": MANIFEST_SCHEMA_VERSION, + } + canonical = json.dumps(payload, ensure_ascii=False, separators=(",", ":"), sort_keys=True) + return hashlib.sha256(canonical.encode()).hexdigest() + + +def _parse_jpeg_properties(data: bytes) -> _JpegProperties: + if not data.startswith(b"\xff\xd8"): + return _JpegProperties(parse_error="missing JPEG SOI marker") + + has_exif = False + try: + for marker, payload in _iter_jpeg_segments(data): + if marker == 0xE1 and payload.startswith(b"Exif\x00\x00"): + has_exif = True + if marker in _START_OF_FRAME_MARKERS: + return _properties_from_sof(marker, payload, has_exif=has_exif) + except ValueError as exc: + return _JpegProperties(has_exif=has_exif, parse_error=str(exc)) + + return _JpegProperties(has_exif=has_exif, parse_error="JPEG start-of-frame marker not found") + + +def _iter_jpeg_segments(data: bytes) -> Iterator[tuple[int, bytes]]: + position = 2 + while position < len(data): + while position < len(data) and data[position] != 0xFF: + position += 1 + while position < len(data) and data[position] == 0xFF: + position += 1 + if position >= len(data): + return + + marker = data[position] + position += 1 + if marker in {0x01, 0xD8, 0xD9} or 0xD0 <= marker <= 0xD7: + continue + if marker == 0xDA: + return + if position + 2 > len(data): + raise ValueError("truncated JPEG segment length") + + segment_length = int.from_bytes(data[position : position + 2], "big") + if segment_length < 2 or position + segment_length > len(data): + raise ValueError("invalid JPEG segment length") + yield marker, data[position + 2 : position + segment_length] + position += segment_length + + +def _properties_from_sof(marker: int, payload: bytes, *, has_exif: bool) -> _JpegProperties: + if len(payload) < 6: + raise ValueError("truncated JPEG start-of-frame segment") + height = int.from_bytes(payload[1:3], "big") + width = int.from_bytes(payload[3:5], "big") + components = payload[5] + return _JpegProperties( + width=width, + height=height, + components=components, + progressive=marker == 0xC2, + subsampling=_read_subsampling(payload, components), + has_exif=has_exif, + ) + + +def _read_subsampling(sof_payload: bytes, components: int) -> str | None: + if components == 1: + return "grayscale" + if components != 3 or len(sof_payload) < 6 + 3 * components: + return None + + sampling = sof_payload[7] + horizontal, vertical = sampling >> 4, sampling & 0x0F + if (horizontal, vertical) == (1, 1): + return "4:4:4" + if (horizontal, vertical) == (2, 1): + return "4:2:2" + if (horizontal, vertical) == (2, 2): + return "4:2:0" + return "other" diff --git a/imread_benchmark/datasets/materializer.py b/imread_benchmark/datasets/materializer.py new file mode 100644 index 0000000..9ef07b4 --- /dev/null +++ b/imread_benchmark/datasets/materializer.py @@ -0,0 +1,293 @@ +from __future__ import annotations + +import hashlib +import importlib +import json +import os +import shutil +import tempfile +from contextlib import AbstractContextManager +from dataclasses import dataclass +from pathlib import Path, PurePosixPath +from typing import Any, Protocol + +from imread_benchmark.datasets.package import DatasetPackageError, open_dataset_package + + +class ObjectStoreError(RuntimeError): + pass + + +class ObjectNotFoundError(ObjectStoreError): + pass + + +class ObjectConflictError(ObjectStoreError): + pass + + +class MaterializationError(ObjectStoreError): + pass + + +@dataclass(frozen=True, slots=True) +class ObjectMetadata: + size: int + generation: str + + +class ObjectStore(Protocol): + def put_create_only(self, source: Path, key: str) -> None: ... + + def download(self, key: str, destination: Path) -> None: ... + + def metadata(self, key: str) -> ObjectMetadata: ... + + +@dataclass(frozen=True, slots=True) +class LocalObjectStore: + root: Path + + def __init__(self, root: str | Path) -> None: + object.__setattr__(self, "root", Path(root).resolve()) + + def put_create_only(self, source: Path, key: str) -> None: + destination = self._path(key) + destination.parent.mkdir(parents=True, exist_ok=True) + if destination.exists(): + if _sha256_file(destination) != _sha256_file(source): + raise ObjectConflictError(f"remote object already exists with different content: {key}") + return + descriptor, temporary_name = tempfile.mkstemp(prefix=f".{destination.name}.", dir=destination.parent) + os.close(descriptor) + temporary = Path(temporary_name) + try: + shutil.copyfile(source, temporary) + try: + os.link(temporary, destination) + except FileExistsError: + if _sha256_file(destination) != _sha256_file(source): + raise ObjectConflictError(f"concurrent writer published different content: {key}") from None + finally: + temporary.unlink(missing_ok=True) + + def download(self, key: str, destination: Path) -> None: + source = self._path(key) + if not source.is_file(): + raise ObjectNotFoundError(f"remote object does not exist: {key}") + destination.parent.mkdir(parents=True, exist_ok=True) + shutil.copyfile(source, destination) + + def metadata(self, key: str) -> ObjectMetadata: + path = self._path(key) + if not path.is_file(): + raise ObjectNotFoundError(f"remote object does not exist: {key}") + stat = path.stat() + return ObjectMetadata(size=stat.st_size, generation=f"{stat.st_ino}:{stat.st_mtime_ns}") + + def _path(self, key: str) -> Path: + relative = _safe_key(key) + path = (self.root / relative).resolve() + if not path.is_relative_to(self.root): + raise MaterializationError(f"object key escapes store root: {key!r}") + return path + + +def publish_dataset_package( + descriptor_path: str | Path, + *, + store: ObjectStore, + prefix: str, +) -> str: + descriptor_source = Path(descriptor_path).resolve() + try: + package = open_dataset_package(descriptor_source) + except DatasetPackageError as exc: + raise MaterializationError(f"cannot publish invalid dataset package: {exc}") from exc + package_id = _required_string(package.descriptor, "package_id") + remote_root = _join_key(prefix, package_id) + relative_files = _package_files(package.descriptor) + for relative in relative_files: + source = package.root / relative + key = _join_key(remote_root, relative) + store.put_create_only(source, key) + metadata = store.metadata(key) + if metadata.size != source.stat().st_size: + raise MaterializationError(f"remote object size mismatch after upload: {key}") + return _join_key(remote_root, "package.json") + + +def materialize_dataset_package( + remote_descriptor: str, + *, + store: ObjectStore, + cache_root: str | Path, +) -> Path: + cache = Path(cache_root).resolve() + cache.mkdir(parents=True, exist_ok=True) + staging_root = Path(tempfile.mkdtemp(prefix=".materialize.", dir=cache)) + try: + bootstrap_descriptor = staging_root / "package.json" + store.download(remote_descriptor, bootstrap_descriptor) + descriptor = _read_object(bootstrap_descriptor) + package_id = _required_string(descriptor, "package_id") + destination = cache / package_id + with _FileLock(cache / f".{package_id}.lock"): + if destination.exists(): + _open_ready(destination / "package.json") + return destination / "package.json" + package_staging = staging_root / package_id + package_staging.mkdir() + bootstrap_descriptor.rename(package_staging / "package.json") + remote_root = str(PurePosixPath(_safe_key(remote_descriptor)).parent) + for relative in _package_files(descriptor): + if relative == "package.json": + continue + store.download(_join_key(remote_root, relative), package_staging / relative) + _verify_and_mark(package_staging / "package.json") + package_staging.rename(destination) + return destination / "package.json" + except (DatasetPackageError, OSError, TypeError, ValueError) as exc: + raise MaterializationError(f"dataset materialization checksum or identity failure: {exc}") from exc + finally: + shutil.rmtree(staging_root, ignore_errors=True) + + +def _verify_and_mark(descriptor_path: Path) -> None: + package = open_dataset_package(descriptor_path) + package_id = _required_string(package.descriptor, "package_id") + workloads = package.descriptor.get("workloads") + if not isinstance(workloads, dict): + raise MaterializationError("dataset descriptor has no workloads object") + marker_root = package.root / ".CONTENT_VERIFIED" + marker_root.mkdir() + for workload_id, workload in workloads.items(): + if not isinstance(workload_id, str) or not isinstance(workload, dict): + raise MaterializationError("dataset descriptor has an invalid workload") + items = package.read_workload_items(workload_id) + _write_json( + marker_root / f"{workload_id}.json", + { + "item_count": len(items), + "manifest_id": workload.get("manifest_id"), + "package_id": package_id, + "schema_version": "2.0", + }, + ) + archive = package.descriptor.get("archive") + if not isinstance(archive, dict): + raise MaterializationError("dataset descriptor has no archive object") + archive_path = package.root / _required_string(archive, "file") + archive_stat = archive_path.stat() + _write_json( + package.root / ".READY.json", + { + "archive": { + "bytes": archive_stat.st_size, + "file": archive_path.name, + "inode": archive_stat.st_ino, + "mtime_ns": archive_stat.st_mtime_ns, + "sha256": archive.get("sha256"), + }, + "descriptor_sha256": _sha256_file(descriptor_path), + "package_id": package_id, + "schema_version": "2.0", + }, + ) + for path in package.root.rglob("*"): + if path.is_file(): + path.chmod(0o444) + + +def _open_ready(descriptor_path: Path) -> None: + try: + open_dataset_package(descriptor_path, trust_ready=True) + except DatasetPackageError as exc: + raise MaterializationError(f"existing materialized package is invalid: {exc}") from exc + + +def _package_files(descriptor: dict[str, Any]) -> tuple[str, ...]: + archive = descriptor.get("archive") + archive_index = descriptor.get("archive_index") + workloads = descriptor.get("workloads") + if not isinstance(archive, dict) or not isinstance(archive_index, dict) or not isinstance(workloads, dict): + raise MaterializationError("dataset descriptor is missing package file references") + files = { + "package.json", + _required_string(archive, "file"), + _required_string(archive_index, "file"), + } + for workload in workloads.values(): + if not isinstance(workload, dict): + raise MaterializationError("dataset descriptor has an invalid workload") + files.add(_required_string(workload, "manifest")) + for relative in files: + _safe_key(relative) + return tuple(sorted(files)) + + +class _FileLock(AbstractContextManager[None]): + def __init__(self, path: Path) -> None: + self.path = path + self.file: Any = None + + def __enter__(self) -> None: + self.file = self.path.open("a+b") + if os.name == "nt": + msvcrt: Any = importlib.import_module("msvcrt") + msvcrt.locking(self.file.fileno(), msvcrt.LK_LOCK, 1) + else: + import fcntl + + fcntl.flock(self.file.fileno(), fcntl.LOCK_EX) + + def __exit__(self, exc_type: object, exc_value: object, traceback: object) -> None: + if self.file is None: + return + if os.name == "nt": + msvcrt: Any = importlib.import_module("msvcrt") + self.file.seek(0) + msvcrt.locking(self.file.fileno(), msvcrt.LK_UNLCK, 1) + else: + import fcntl + + fcntl.flock(self.file.fileno(), fcntl.LOCK_UN) + self.file.close() + + +def _safe_key(key: str) -> str: + path = PurePosixPath(key) + if path.is_absolute() or not path.parts or ".." in path.parts or "." in path.parts: + raise MaterializationError(f"unsafe object key: {key!r}") + return str(path) + + +def _join_key(*parts: str) -> str: + return _safe_key(str(PurePosixPath(*parts))) + + +def _read_object(path: Path) -> dict[str, Any]: + value = json.loads(path.read_text()) + if not isinstance(value, dict): + raise TypeError(f"{path} must contain a JSON object") + return value + + +def _required_string(payload: dict[str, Any], key: str) -> str: + value = payload.get(key) + if not isinstance(value, str) or not value: + raise MaterializationError(f"field {key!r} must be a non-empty string") + return value + + +def _write_json(path: Path, payload: object) -> None: + path.parent.mkdir(parents=True, exist_ok=True) + path.write_text(json.dumps(payload, ensure_ascii=False, indent=2, sort_keys=True) + "\n") + + +def _sha256_file(path: Path) -> str: + digest = hashlib.sha256() + with path.open("rb") as file: + while chunk := file.read(1024 * 1024): + digest.update(chunk) + return digest.hexdigest() diff --git a/imread_benchmark/datasets/package.py b/imread_benchmark/datasets/package.py new file mode 100644 index 0000000..c1d8918 --- /dev/null +++ b/imread_benchmark/datasets/package.py @@ -0,0 +1,424 @@ +from __future__ import annotations + +import hashlib +import io +import json +import re +import shutil +import tarfile +import tempfile +from dataclasses import dataclass +from pathlib import Path, PurePosixPath +from typing import TYPE_CHECKING, Any + +from imread_benchmark.datasets.manifest import DatasetManifest + +if TYPE_CHECKING: + from collections.abc import Mapping + +PACKAGE_SCHEMA_VERSION = "2.0" +_SAFE_ID = re.compile(r"^[A-Za-z0-9][A-Za-z0-9._-]*$") + + +class DatasetPackageError(ValueError): + pass + + +@dataclass(frozen=True, slots=True) +class ResidentItem: + item_id: str + relative_path: str + sha256: str + compressed_bytes: int + data: bytes + + +@dataclass(frozen=True, slots=True) +class DatasetPackageReader: + root: Path + descriptor: dict[str, Any] + archive_index: dict[str, dict[str, int | str]] + verify_item_hashes: bool = True + + def read_workload(self, workload_id: str) -> tuple[bytes, ...]: + return tuple(item.data for item in self.read_workload_items(workload_id)) + + def read_workload_items(self, workload_id: str) -> tuple[ResidentItem, ...]: + workload = _workload(self.descriptor, workload_id) + manifest_path = _resolve_member(self.root, _required_string(workload, "manifest")) + manifest = _read_object(manifest_path) + items = manifest.get("items") + if not isinstance(items, list): + raise DatasetPackageError(f"manifest for {workload_id!r} has no items list") + + archive = _archive_path(self.root, self.descriptor) + blobs: dict[str, bytes] = {} + with archive.open("rb") as file: + for raw_item in items: + if not isinstance(raw_item, dict): + raise DatasetPackageError(f"manifest for {workload_id!r} contains a non-object item") + digest = _required_string(raw_item, "sha256") + if digest in blobs: + continue + entry = self.archive_index.get(digest) + if entry is None: + raise DatasetPackageError(f"archive index has no blob {digest}") + offset = entry.get("offset") + size = entry.get("size") + if not isinstance(offset, int) or not isinstance(size, int) or offset < 0 or size <= 0: + raise DatasetPackageError(f"archive index entry for {digest} has invalid offset or size") + file.seek(offset) + data = file.read(size) + if len(data) != size or (self.verify_item_hashes and hashlib.sha256(data).hexdigest() != digest): + raise DatasetPackageError(f"archive blob {digest} failed content verification") + blobs[digest] = data + return tuple( + ResidentItem( + item_id=_required_string(item, "item_id"), + relative_path=_required_string(item, "relative_path"), + sha256=_required_string(item, "sha256"), + compressed_bytes=_required_positive_int(item, "compressed_bytes"), + data=blobs[_required_string(item, "sha256")], + ) + for item in items + if isinstance(item, dict) + ) + + +def build_dataset_package( + *, + package_name: str, + workloads: Mapping[str, str | Path], + output_root: str | Path, + provenance: Mapping[str, object], +) -> Path: + _validate_id(package_name, field="package_name") + if not workloads: + raise DatasetPackageError("at least one workload is required") + resolved_workloads = _resolve_workloads(workloads) + + destination_root = Path(output_root).resolve() + destination_root.mkdir(parents=True, exist_ok=True) + staging = Path(tempfile.mkdtemp(prefix=".dataset-package.", dir=destination_root)) + try: + manifests, manifest_files, manifest_ids, blob_sources = _build_manifests(resolved_workloads, staging) + + temporary_archive = staging / "payload.tar" + with tarfile.open(temporary_archive, mode="w", format=tarfile.PAX_FORMAT) as archive: + for digest, source in sorted(blob_sources.items()): + _add_file(archive, source, f"blobs/{digest}.jpg") + for manifest_file in sorted(manifest_files.values()): + _add_bytes(archive, (staging / manifest_file).read_bytes(), manifest_file) + + archive_index = _build_archive_index(temporary_archive) + index_path = staging / "archive-index.json" + _write_json( + index_path, + { + "blobs": archive_index, + "schema_version": PACKAGE_SCHEMA_VERSION, + }, + ) + archive_sha256 = _sha256_file(temporary_archive) + archive_name = f"{archive_sha256}.tar" + temporary_archive.rename(staging / archive_name) + index_sha256 = _sha256_file(index_path) + + descriptor_without_id: dict[str, object] = { + "archive": { + "bytes": (staging / archive_name).stat().st_size, + "file": archive_name, + "format": "tar", + "sha256": archive_sha256, + }, + "archive_index": { + "file": index_path.name, + "sha256": index_sha256, + }, + "package_name": package_name, + "provenance": dict(provenance), + "schema_version": PACKAGE_SCHEMA_VERSION, + "workloads": { + workload_id: { + "item_count": len(manifest.items), + "manifest": manifest_files[workload_id], + "manifest_id": manifest_ids[workload_id], + } + for workload_id, manifest in sorted(manifests.items()) + }, + } + package_id = _digest_json(descriptor_without_id) + descriptor = {**descriptor_without_id, "package_id": package_id} + _write_json(staging / "package.json", descriptor) + + destination = destination_root / package_id + if destination.exists(): + existing = _read_object(destination / "package.json") + if existing != descriptor: + raise DatasetPackageError(f"existing package {package_id} has different content") + return destination / "package.json" + staging.rename(destination) + return destination / "package.json" + finally: + if staging.exists(): + shutil.rmtree(staging) + + +def _resolve_workloads(workloads: Mapping[str, str | Path]) -> dict[str, Path]: + resolved: dict[str, Path] = {} + for workload_id, root in workloads.items(): + _validate_id(workload_id, field="workload_id") + path = Path(root).resolve() + if not path.is_dir(): + raise DatasetPackageError(f"workload directory does not exist: {path}") + resolved[workload_id] = path + return resolved + + +def _build_manifests( + workloads: Mapping[str, Path], + staging: Path, +) -> tuple[dict[str, DatasetManifest], dict[str, str], dict[str, str], dict[str, Path]]: + manifests: dict[str, DatasetManifest] = {} + manifest_files: dict[str, str] = {} + manifest_ids: dict[str, str] = {} + blob_sources: dict[str, Path] = {} + for workload_id, root in sorted(workloads.items()): + manifest = DatasetManifest.build(root, dataset_name=workload_id) + manifests[workload_id] = manifest + manifest_file = f"manifests/{workload_id}.json" + manifest_files[workload_id] = manifest_file + manifest_payload = _package_manifest(manifest) + manifest_ids[workload_id] = _required_string(manifest_payload, "manifest_id") + _write_json(staging / manifest_file, manifest_payload) + for item in manifest.items: + blob_sources.setdefault(item.sha256, manifest.resolve(item)) + return manifests, manifest_files, manifest_ids, blob_sources + + +def _package_manifest(manifest: DatasetManifest) -> dict[str, object]: + payload = { + "dataset_name": manifest.dataset_name, + "items": [item.to_dict() for item in manifest.items], + "schema_version": PACKAGE_SCHEMA_VERSION, + } + return {**payload, "manifest_id": _digest_json(payload)} + + +def open_dataset_package( + descriptor_path: str | Path, + *, + trust_ready: bool = False, +) -> DatasetPackageReader: + path = Path(descriptor_path).resolve() + descriptor = _read_object(path) + if descriptor.get("schema_version") != PACKAGE_SCHEMA_VERSION: + raise DatasetPackageError(f"unsupported dataset package schema: {descriptor.get('schema_version')!r}") + package_id = descriptor.get("package_id") + if not isinstance(package_id, str): + raise DatasetPackageError("dataset package has no package_id") + without_id = {key: value for key, value in descriptor.items() if key != "package_id"} + if _digest_json(without_id) != package_id or path.parent.name != package_id: + raise DatasetPackageError("dataset package identity mismatch") + + archive_path = _archive_path(path.parent, descriptor) + archive = _required_object(descriptor, "archive") + if trust_ready: + _validate_ready_package(path, descriptor, archive_path) + elif archive_path.stat().st_size != archive.get("bytes") or _sha256_file(archive_path) != archive.get("sha256"): + raise DatasetPackageError("dataset package archive checksum mismatch") + + index_descriptor = _required_object(descriptor, "archive_index") + index_path = _resolve_member(path.parent, _required_string(index_descriptor, "file")) + if _sha256_file(index_path) != index_descriptor.get("sha256"): + raise DatasetPackageError("dataset package archive index checksum mismatch") + index_document = _read_object(index_path) + blobs = index_document.get("blobs") + if not isinstance(blobs, dict): + raise DatasetPackageError("dataset package archive index has no blobs object") + if not trust_ready: + _audit_tar_members(archive_path) + if _build_archive_index(archive_path) != blobs: + raise DatasetPackageError("dataset package archive index does not match tar members") + _validate_workload_manifests(path.parent, descriptor) + return DatasetPackageReader( + root=path.parent, + descriptor=descriptor, + archive_index=blobs, + verify_item_hashes=not trust_ready, + ) + + +def _validate_ready_package(path: Path, descriptor: dict[str, Any], archive_path: Path) -> None: + ready = _read_object(path.parent / ".READY.json") + archive_stat = archive_path.stat() + archive_ready = ready.get("archive") + workloads = _required_object(descriptor, "workloads") + if ( + ready.get("schema_version") != PACKAGE_SCHEMA_VERSION + or ready.get("package_id") != descriptor.get("package_id") + or ready.get("descriptor_sha256") != _sha256_file(path) + or not isinstance(archive_ready, dict) + or archive_ready.get("file") != archive_path.name + or archive_ready.get("bytes") != archive_stat.st_size + or archive_ready.get("mtime_ns") != archive_stat.st_mtime_ns + or archive_ready.get("inode") != archive_stat.st_ino + ): + raise DatasetPackageError("dataset package .READY identity mismatch") + for workload_id, workload in workloads.items(): + if not isinstance(workload, dict): + raise DatasetPackageError(f"dataset package workload {workload_id!r} must be an object") + marker = _read_object(path.parent / ".CONTENT_VERIFIED" / f"{workload_id}.json") + if ( + marker.get("package_id") != descriptor.get("package_id") + or marker.get("manifest_id") != workload.get("manifest_id") + or marker.get("item_count") != workload.get("item_count") + ): + raise DatasetPackageError(f"workload {workload_id!r} content verification marker mismatch") + + +def _audit_tar_members(path: Path) -> None: + with tarfile.open(path, mode="r:") as archive: + for member in archive: + member_path = PurePosixPath(member.name) + if member_path.is_absolute() or ".." in member_path.parts or not member.isfile(): + raise DatasetPackageError(f"unsafe tar member: {member.name!r}") + + +def _validate_workload_manifests(root: Path, descriptor: dict[str, Any]) -> None: + workloads = _required_object(descriptor, "workloads") + for workload_id, raw_workload in workloads.items(): + if not isinstance(raw_workload, dict): + raise DatasetPackageError(f"dataset package workload {workload_id!r} must be an object") + manifest = _read_object(_resolve_member(root, _required_string(raw_workload, "manifest"))) + stored_manifest_id = manifest.get("manifest_id") + manifest_without_id = {key: value for key, value in manifest.items() if key != "manifest_id"} + expected_manifest_id = raw_workload.get("manifest_id") + items = manifest.get("items") + if ( + manifest.get("schema_version") != PACKAGE_SCHEMA_VERSION + or not isinstance(stored_manifest_id, str) + or _digest_json(manifest_without_id) != stored_manifest_id + or stored_manifest_id != expected_manifest_id + or not isinstance(items, list) + or len(items) != raw_workload.get("item_count") + ): + raise DatasetPackageError(f"workload {workload_id!r} manifest identity mismatch") + + +def _build_archive_index(path: Path) -> dict[str, dict[str, int | str]]: + index: dict[str, dict[str, int | str]] = {} + with tarfile.open(path, mode="r:") as archive: + for member in archive: + if not member.isfile() or not member.name.startswith("blobs/"): + continue + digest = Path(member.name).stem + index[digest] = { + "member": member.name, + "offset": member.offset_data, + "size": member.size, + } + return index + + +def _add_file(archive: tarfile.TarFile, source: Path, member_name: str) -> None: + info = _tar_info(member_name, source.stat().st_size) + with source.open("rb") as file: + archive.addfile(info, file) + + +def _add_bytes(archive: tarfile.TarFile, data: bytes, member_name: str) -> None: + archive.addfile(_tar_info(member_name, len(data)), io.BytesIO(data)) + + +def _tar_info(member_name: str, size: int) -> tarfile.TarInfo: + info = tarfile.TarInfo(member_name) + info.size = size + info.mode = 0o444 + info.mtime = 0 + info.uid = 0 + info.gid = 0 + info.uname = "" + info.gname = "" + return info + + +def _archive_path(root: Path, descriptor: dict[str, Any]) -> Path: + archive = _required_object(descriptor, "archive") + return _resolve_member(root, _required_string(archive, "file")) + + +def _workload(descriptor: dict[str, Any], workload_id: str) -> dict[str, Any]: + workloads = _required_object(descriptor, "workloads") + workload = workloads.get(workload_id) + if not isinstance(workload, dict): + raise DatasetPackageError(f"dataset package has no workload {workload_id!r}") + return workload + + +def _resolve_member(root: Path, relative_path: str) -> Path: + candidate = (root / relative_path).resolve() + if not candidate.is_relative_to(root): + raise DatasetPackageError(f"package path escapes root: {relative_path!r}") + if not candidate.is_file(): + raise DatasetPackageError(f"package file does not exist: {relative_path!r}") + return candidate + + +def _validate_id(value: str, *, field: str) -> None: + if _SAFE_ID.fullmatch(value) is None or value in {".", ".."}: + raise DatasetPackageError(f"{field} must be a safe identifier, got {value!r}") + + +def _required_object(payload: dict[str, Any], key: str) -> dict[str, Any]: + value = payload.get(key) + if not isinstance(value, dict): + raise DatasetPackageError(f"dataset package field {key!r} must be an object") + return value + + +def _required_string(payload: dict[str, Any], key: str) -> str: + value = payload.get(key) + if not isinstance(value, str) or not value: + raise DatasetPackageError(f"dataset package field {key!r} must be a non-empty string") + return value + + +def _required_positive_int(payload: dict[str, Any], key: str) -> int: + value = payload.get(key) + if isinstance(value, bool) or not isinstance(value, int) or value <= 0: + raise DatasetPackageError(f"dataset package field {key!r} must be a positive integer") + return value + + +def _read_object(path: Path) -> dict[str, Any]: + try: + payload = json.loads(path.read_text()) + except (OSError, json.JSONDecodeError) as exc: + raise DatasetPackageError(f"cannot read JSON object {path}: {exc}") from exc + if not isinstance(payload, dict): + raise DatasetPackageError(f"expected JSON object in {path}") + return payload + + +def _write_json(path: Path, payload: object) -> None: + path.parent.mkdir(parents=True, exist_ok=True) + path.write_bytes(_canonical_json(payload, pretty=True)) + + +def _canonical_json(payload: object, *, pretty: bool = False) -> bytes: + if pretty: + return (json.dumps(payload, ensure_ascii=False, indent=2, sort_keys=True) + "\n").encode() + return json.dumps(payload, ensure_ascii=False, separators=(",", ":"), sort_keys=True).encode() + + +def _digest_json(payload: object) -> str: + return hashlib.sha256(_canonical_json(payload)).hexdigest() + + +def _sha256_file(path: Path) -> str: + digest = hashlib.sha256() + with path.open("rb") as file: + while chunk := file.read(8 * 1024 * 1024): + digest.update(chunk) + return digest.hexdigest() diff --git a/imread_benchmark/decoders/__init__.py b/imread_benchmark/decoders/__init__.py index 8b38525..d324675 100644 --- a/imread_benchmark/decoders/__init__.py +++ b/imread_benchmark/decoders/__init__.py @@ -29,6 +29,11 @@ class BaseDecoder(ABC): # nobody wires tf.io.decode_jpeg into a torch worker in practice). in_dataloader: ClassVar[bool] = True + # Existing adapters expose the benchmark's normalized HWC uint8 RGB + # result. A future adapter may opt into a separate native-output method; + # declaring this explicitly prevents "native" from being inferred. + native_output_available: ClassVar[bool] = False + @abstractmethod def decode(self, data: bytes) -> np.ndarray: """Decode JPEG bytes into an (H, W, 3) uint8 RGB numpy array.""" diff --git a/imread_benchmark/decoders/capabilities.py b/imread_benchmark/decoders/capabilities.py new file mode 100644 index 0000000..81f16f1 --- /dev/null +++ b/imread_benchmark/decoders/capabilities.py @@ -0,0 +1,50 @@ +from __future__ import annotations + +from dataclasses import dataclass + +from imread_benchmark.contracts import OutputContract +from imread_benchmark.decoders import BaseDecoder + +CAPABILITY_SCHEMA_VERSION = "2.0" + + +@dataclass(frozen=True, slots=True) +class DecoderCapabilities: + decoder_id: str + package_name: str + dependency_group: str + input_sources: tuple[str, ...] + output_contracts: tuple[OutputContract, ...] + native_output_available: bool + normalization_location: str + thread_control: str + process_compatible: bool + + def to_dict(self) -> dict[str, object]: + return { + "decoder_id": self.decoder_id, + "dependency_group": self.dependency_group, + "input_sources": list(self.input_sources), + "native_output_available": self.native_output_available, + "normalization_location": self.normalization_location, + "output_contracts": [contract.to_dict() for contract in self.output_contracts], + "package_name": self.package_name, + "process_compatible": self.process_compatible, + "schema_version": CAPABILITY_SCHEMA_VERSION, + "thread_control": self.thread_control, + } + + +def describe_decoder(decoder_cls: type[BaseDecoder]) -> DecoderCapabilities: + thread_control = "settable" if decoder_cls.set_num_threads is not BaseDecoder.set_num_threads else "fixed" + return DecoderCapabilities( + decoder_id=decoder_cls.name, + package_name=decoder_cls.package_name, + dependency_group=decoder_cls.group, + input_sources=("bytes", "path"), + output_contracts=(OutputContract.normalized_rgb(),), + native_output_available=decoder_cls.native_output_available, + normalization_location="decoder-adapter", + thread_control=thread_control, + process_compatible=decoder_cls.in_dataloader, + ) diff --git a/imread_benchmark/decoders/kornia_decoder.py b/imread_benchmark/decoders/kornia_decoder.py index 79ecf53..0f3dd2d 100644 --- a/imread_benchmark/decoders/kornia_decoder.py +++ b/imread_benchmark/decoders/kornia_decoder.py @@ -12,11 +12,9 @@ class KorniaDecoder(BaseDecoder): def decode(self, data: bytes) -> np.ndarray: import kornia_rs as K - return K.decode_image_jpeg(data) + return K.io.decode_image_jpeg(data) def decode_path(self, path: str) -> np.ndarray: import kornia_rs as K - # Newer kornia_rs (>= 0.1.x) requires an explicit colour mode. "rgb" - # matches what every other decoder in the suite produces. - return K.read_image_jpeg(path, "rgb") + return K.io.read_image_jpeg(path, "rgb") diff --git a/imread_benchmark/decoders/opencv_decoder.py b/imread_benchmark/decoders/opencv_decoder.py index 0360bcf..e577912 100644 --- a/imread_benchmark/decoders/opencv_decoder.py +++ b/imread_benchmark/decoders/opencv_decoder.py @@ -13,12 +13,18 @@ def decode(self, data: bytes) -> np.ndarray: import cv2 buf = np.frombuffer(data, dtype=np.uint8) - return cv2.imdecode(buf, cv2.IMREAD_COLOR_RGB) + decoded = cv2.imdecode(buf, cv2.IMREAD_COLOR_RGB) + if decoded is None: + raise ValueError("OpenCV could not decode the JPEG bytes") + return decoded def decode_path(self, path: str) -> np.ndarray: import cv2 - return cv2.imread(path, cv2.IMREAD_COLOR_RGB) + decoded = cv2.imread(path, cv2.IMREAD_COLOR_RGB) + if decoded is None: + raise ValueError(f"OpenCV could not decode JPEG path: {path}") + return decoded def get_num_threads(self) -> int: import cv2 diff --git a/imread_benchmark/decoders/pillow_decoder.py b/imread_benchmark/decoders/pillow_decoder.py index 90c0fd9..9044473 100644 --- a/imread_benchmark/decoders/pillow_decoder.py +++ b/imread_benchmark/decoders/pillow_decoder.py @@ -14,9 +14,17 @@ class PillowDecoder(BaseDecoder): def decode(self, data: bytes) -> np.ndarray: from PIL import Image - return np.asarray(Image.open(BytesIO(data)).convert("RGB")) + with Image.open(BytesIO(data)) as image: + image.load() + rgb = image.convert("RGB") + rgb.load() + return np.array(rgb, dtype=np.uint8, copy=True) def decode_path(self, path: str) -> np.ndarray: from PIL import Image - return np.asarray(Image.open(path).convert("RGB")) + with Image.open(path) as image: + image.load() + rgb = image.convert("RGB") + rgb.load() + return np.array(rgb, dtype=np.uint8, copy=True) diff --git a/imread_benchmark/decoders/pyvips_decoder.py b/imread_benchmark/decoders/pyvips_decoder.py index acdbbe3..5ff26d8 100644 --- a/imread_benchmark/decoders/pyvips_decoder.py +++ b/imread_benchmark/decoders/pyvips_decoder.py @@ -21,21 +21,6 @@ class PyVipsDecoder(BaseDecoder): name = "pyvips" package_name = "pyvips" - # libvips spawns a GLib worker threadpool at module import. PyTorch - # DataLoader with num_workers>0 forks the parent; the child inherits - # pthread IDs but not the threads themselves → first libvips call in the - # worker deadlocks waiting on a thread that doesn't exist. - # - # Originally scoped to Linux/aarch64 with a "x86 + macOS get away with - # it" caveat — empirically wrong: c4-standard-16 (Intel, 16 vCPU) hung - # at workers=2. More vCPUs ⇒ larger libvips threadpool ⇒ more - # opportunities for the fork race. - # - # `multiprocessing_context='spawn'` would dodge it but pays full Python - # re-import per worker, which is not how anyone runs pyvips in practice - # — the resulting numbers would be misleading. The honest finding is - # "pyvips is incompatible with the default fork-based DataLoader". - in_dataloader = False def decode(self, data: bytes) -> np.ndarray: import pyvips diff --git a/imread_benchmark/decoders/skimage_decoder.py b/imread_benchmark/decoders/skimage_decoder.py index f978d40..3985a4e 100644 --- a/imread_benchmark/decoders/skimage_decoder.py +++ b/imread_benchmark/decoders/skimage_decoder.py @@ -12,11 +12,11 @@ class SkimageDecoder(BaseDecoder): def decode(self, data: bytes) -> np.ndarray: import io - import skimage.io + from skimage.io import imread - return skimage.io.imread(io.BytesIO(data)) + return imread(io.BytesIO(data)) def decode_path(self, path: str) -> np.ndarray: - import skimage.io + from skimage.io import imread - return skimage.io.imread(path) + return imread(path) diff --git a/imread_benchmark/environments/__init__.py b/imread_benchmark/environments/__init__.py new file mode 100644 index 0000000..1acef7d --- /dev/null +++ b/imread_benchmark/environments/__init__.py @@ -0,0 +1,25 @@ +from imread_benchmark.environments.descriptor import ( + ENVIRONMENT_SCHEMA_VERSION, + EnvironmentDescriptor, + capture_current_environment, + load_environment_descriptor, + write_environment_descriptor, +) +from imread_benchmark.environments.provision import ( + EnvironmentRequest, + ProvisionedEnvironment, + load_provisioned_environment, + provision_environment, +) + +__all__ = [ + "ENVIRONMENT_SCHEMA_VERSION", + "EnvironmentDescriptor", + "EnvironmentRequest", + "ProvisionedEnvironment", + "capture_current_environment", + "load_environment_descriptor", + "load_provisioned_environment", + "provision_environment", + "write_environment_descriptor", +] diff --git a/imread_benchmark/environments/cache.py b/imread_benchmark/environments/cache.py new file mode 100644 index 0000000..ac1d53c --- /dev/null +++ b/imread_benchmark/environments/cache.py @@ -0,0 +1,210 @@ +from __future__ import annotations + +import hashlib +import json +import shutil +import tarfile +import tempfile +from contextlib import suppress +from pathlib import Path, PurePosixPath +from typing import Any + +import zstandard + +from imread_benchmark.datasets.materializer import ObjectConflictError, ObjectNotFoundError, ObjectStore +from imread_benchmark.environments.descriptor import load_environment_descriptor +from imread_benchmark.environments.provision import ( + EnvironmentRequest, + ProvisionedEnvironment, + load_provisioned_environment, +) + +ENVIRONMENT_CACHE_SCHEMA_VERSION = "2.0" + + +def publish_environment_cache( + environment_root: str | Path, + *, + store: ObjectStore, + prefix: str, +) -> str: + root = Path(environment_root).resolve() + ready = _read_object(root / ".READY.json") + environment_key = _required_string(ready, "environment_key") + environment_id = _required_string(ready, "environment_id") + descriptor = load_environment_descriptor(root / "environment.json") + if descriptor.environment_id != environment_id: + raise ValueError("environment cache READY marker does not match descriptor") + marker_key = _key(prefix, "keys", f"{environment_key}.json") + existing = _download_optional_json(store, marker_key) + if existing is not None: + _validate_marker(existing, environment_key=environment_key, environment_id=environment_id) + return marker_key + + with tempfile.TemporaryDirectory(prefix="environment-cache-publish.") as temporary_directory: + archive = Path(temporary_directory) / "environment.tar.zst" + _write_deterministic_archive(root, archive) + archive_sha256 = _sha256_file(archive) + archive_key = _key(prefix, "blobs", f"{archive_sha256}.tar.zst") + store.put_create_only(archive, archive_key) + marker_path = Path(temporary_directory) / "marker.json" + marker = { + "archive": { + "bytes": archive.stat().st_size, + "key": archive_key, + "sha256": archive_sha256, + }, + "environment_id": environment_id, + "environment_key": environment_key, + "schema_version": ENVIRONMENT_CACHE_SCHEMA_VERSION, + "status": "committed", + } + marker_path.write_text(json.dumps(marker, ensure_ascii=False, indent=2, sort_keys=True) + "\n") + try: + store.put_create_only(marker_path, marker_key) + except ObjectConflictError: + winner = _download_optional_json(store, marker_key) + if winner is None: + raise + _validate_marker(winner, environment_key=environment_key, environment_id=environment_id) + return marker_key + + +def materialize_environment_cache( + request: EnvironmentRequest, + *, + store: ObjectStore, + prefix: str, +) -> ProvisionedEnvironment | None: + destination = request.cache_root / request.environment_key + if destination.exists(): + return load_provisioned_environment(destination, request, cache_hit=True) + marker_key = _key(prefix, "keys", f"{request.environment_key}.json") + marker = _download_optional_json(store, marker_key) + if marker is None: + return None + environment_id = _required_string(marker, "environment_id") + _validate_marker(marker, environment_key=request.environment_key, environment_id=environment_id) + archive_metadata = marker.get("archive") + if not isinstance(archive_metadata, dict): + raise TypeError("environment cache marker archive must be an object") + archive_key = _required_string(archive_metadata, "key") + archive_sha256 = _required_string(archive_metadata, "sha256") + archive_bytes = archive_metadata.get("bytes") + if isinstance(archive_bytes, bool) or not isinstance(archive_bytes, int) or archive_bytes <= 0: + raise ValueError("environment cache marker archive bytes must be positive") + + request.cache_root.mkdir(parents=True, exist_ok=True) + staging_parent = Path(tempfile.mkdtemp(prefix=f".{request.environment_key}.", dir=request.cache_root)) + try: + compressed = staging_parent / "environment.tar.zst" + store.download(archive_key, compressed) + if compressed.stat().st_size != archive_bytes or _sha256_file(compressed) != archive_sha256: + raise ValueError("environment cache archive checksum mismatch") + tar_path = staging_parent / "environment.tar" + _decompress_archive(compressed, tar_path) + staging = staging_parent / "environment" + staging.mkdir() + _extract_safe_tar(tar_path, staging) + restored = load_provisioned_environment(staging, request, cache_hit=True) + if restored.environment_id != environment_id: + raise ValueError("restored environment_id does not match remote marker") + with suppress(FileExistsError): + staging.rename(destination) + return load_provisioned_environment(destination, request, cache_hit=True) + finally: + shutil.rmtree(staging_parent, ignore_errors=True) + + +def _write_deterministic_archive(root: Path, destination: Path) -> None: + compressor = zstandard.ZstdCompressor(level=3, threads=0) + with ( + destination.open("wb") as compressed, + compressor.stream_writer(compressed, closefd=False) as stream, + tarfile.open(fileobj=stream, mode="w|", format=tarfile.GNU_FORMAT, dereference=True) as archive, + ): + for path in sorted(root.rglob("*"), key=lambda value: value.relative_to(root).as_posix()): + archive.add( + path, + arcname=path.relative_to(root).as_posix(), + recursive=False, + filter=_normalized_tar_info, + ) + + +def _normalized_tar_info(info: tarfile.TarInfo) -> tarfile.TarInfo: + info.mtime = 0 + info.uid = 0 + info.gid = 0 + info.uname = "" + info.gname = "" + return info + + +def _decompress_archive(source: Path, destination: Path) -> None: + decompressor = zstandard.ZstdDecompressor() + with source.open("rb") as compressed, destination.open("wb") as target: + decompressor.copy_stream(compressed, target) + + +def _extract_safe_tar(source: Path, destination: Path) -> None: + with tarfile.open(source, mode="r:") as archive: + for member in archive.getmembers(): + path = PurePosixPath(member.name) + if path.is_absolute() or not path.parts or ".." in path.parts: + raise ValueError(f"unsafe environment cache member: {member.name!r}") + if not (member.isdir() or member.isfile() or member.issym()): + raise ValueError(f"unsupported environment cache member type: {member.name!r}") + archive.extractall(destination, filter="data") + + +def _download_optional_json(store: ObjectStore, key: str) -> dict[str, Any] | None: + with tempfile.TemporaryDirectory(prefix="environment-cache-marker.") as directory: + path = Path(directory) / "marker.json" + try: + store.download(key, path) + except ObjectNotFoundError: + return None + return _read_object(path) + + +def _validate_marker(document: dict[str, Any], *, environment_key: str, environment_id: str) -> None: + if ( + document.get("schema_version") != ENVIRONMENT_CACHE_SCHEMA_VERSION + or document.get("status") != "committed" + or document.get("environment_key") != environment_key + or document.get("environment_id") != environment_id + ): + raise ValueError("environment cache marker identity mismatch") + + +def _key(*parts: str) -> str: + path = PurePosixPath(*parts) + if path.is_absolute() or not path.parts or "." in path.parts or ".." in path.parts: + raise ValueError(f"unsafe environment cache object key: {path}") + return str(path) + + +def _read_object(path: Path) -> dict[str, Any]: + try: + value = json.loads(path.read_text()) + except (OSError, json.JSONDecodeError) as exc: + raise ValueError(f"cannot read environment cache JSON {path}: {exc}") from exc + if not isinstance(value, dict): + raise TypeError("environment cache JSON must be an object") + return value + + +def _required_string(payload: dict[str, Any], key: str) -> str: + value = payload.get(key) + if not isinstance(value, str) or not value: + raise ValueError(f"environment cache field {key!r} must be a non-empty string") + return value + + +def _sha256_file(path: Path) -> str: + digest = hashlib.sha256() + with path.open("rb") as file: + for block in iter(lambda: file.read(1024 * 1024), b""): + digest.update(block) + return digest.hexdigest() diff --git a/imread_benchmark/environments/descriptor.py b/imread_benchmark/environments/descriptor.py new file mode 100644 index 0000000..0cf2c2e --- /dev/null +++ b/imread_benchmark/environments/descriptor.py @@ -0,0 +1,270 @@ +from __future__ import annotations + +import hashlib +import importlib.metadata +import json +import os +import platform +import re +import sys +import sysconfig +import tempfile +from dataclasses import dataclass +from pathlib import Path +from typing import Any + +ENVIRONMENT_SCHEMA_VERSION = "2.0" +_DIGEST = re.compile(r"^[0-9a-f]{64}$") +_REVISION = re.compile(r"^[0-9a-f]{40}(?:[0-9a-f]{24})?$") + + +@dataclass(frozen=True, slots=True) +class EnvironmentDescriptor: + environment_id: str + dependency_group: str + lock_sha256: str + project_sha256: str + runner_revision: str + python: dict[str, str] + platform_tags: tuple[str, ...] + distributions: tuple[tuple[str, str], ...] + native_backends: dict[str, str] + + @classmethod + def build( # noqa: PLR0913 - every provenance input is an independent identity dimension + cls, + *, + dependency_group: str, + lock_sha256: str, + project_sha256: str, + runner_revision: str, + python: dict[str, str], + platform_tags: tuple[str, ...], + distributions: tuple[tuple[str, str], ...], + native_backends: dict[str, str], + ) -> EnvironmentDescriptor: + if not dependency_group: + raise ValueError("dependency_group must not be empty") + _validate_digest(lock_sha256, "lock_sha256") + _validate_digest(project_sha256, "project_sha256") + if _REVISION.fullmatch(runner_revision) is None: + raise ValueError("runner_revision must be a 40- or 64-character hexadecimal revision") + normalized_python = _string_mapping(python, "python") + if set(normalized_python) != {"abi", "implementation", "version"}: + raise ValueError("python descriptor must contain abi, implementation, and version") + if not platform_tags or any(not tag for tag in platform_tags): + raise ValueError("platform_tags must not be empty") + normalized_distributions = tuple(sorted((_normalize_name(name), version) for name, version in distributions)) + if not normalized_distributions or any(not version for _, version in normalized_distributions): + raise ValueError("distributions must contain installed package names and versions") + if len({name for name, _ in normalized_distributions}) != len(normalized_distributions): + raise ValueError("distributions contain duplicate package names") + normalized_backends = _string_mapping(native_backends, "native_backends", allow_empty=True) + normalized_tags = tuple(sorted(set(platform_tags))) + identity = { + "dependency_group": dependency_group, + "distributions": _distribution_documents(normalized_distributions), + "lock_sha256": lock_sha256, + "native_backends": normalized_backends, + "platform_tags": list(normalized_tags), + "project_sha256": project_sha256, + "python": normalized_python, + "runner_revision": runner_revision, + "schema_version": ENVIRONMENT_SCHEMA_VERSION, + } + return cls( + environment_id=_digest(identity), + dependency_group=dependency_group, + lock_sha256=lock_sha256, + project_sha256=project_sha256, + runner_revision=runner_revision, + python=normalized_python, + platform_tags=normalized_tags, + distributions=normalized_distributions, + native_backends=normalized_backends, + ) + + def to_dict(self) -> dict[str, object]: + return { + "dependency_group": self.dependency_group, + "distributions": _distribution_documents(self.distributions), + "environment_id": self.environment_id, + "lock_sha256": self.lock_sha256, + "native_backends": self.native_backends, + "platform_tags": list(self.platform_tags), + "project_sha256": self.project_sha256, + "python": self.python, + "runner_revision": self.runner_revision, + "schema_version": ENVIRONMENT_SCHEMA_VERSION, + } + + +def capture_current_environment( + *, + lock_path: str | Path, + project_path: str | Path, + dependency_group: str, + runner_revision: str, + native_backends: dict[str, str] | None = None, +) -> EnvironmentDescriptor: + distributions: list[tuple[str, str]] = [] + editable: list[str] = [] + for distribution in importlib.metadata.distributions(): + name = distribution.metadata["Name"] + if not name: + continue + distributions.append((name, distribution.version)) + direct_url_text = distribution.read_text("direct_url.json") + if direct_url_text: + try: + direct_url = json.loads(direct_url_text) + except json.JSONDecodeError as exc: + raise ValueError(f"invalid direct_url.json for installed distribution {name}") from exc + directory_info = direct_url.get("dir_info") if isinstance(direct_url, dict) else None + if isinstance(directory_info, dict) and directory_info.get("editable") is True: + editable.append(name) + if editable: + raise ValueError(f"editable distributions are forbidden: {', '.join(sorted(editable))}") + abi = sysconfig.get_config_var("SOABI") or sys.implementation.cache_tag or "unknown" + return EnvironmentDescriptor.build( + dependency_group=dependency_group, + lock_sha256=_sha256_file(Path(lock_path)), + project_sha256=_sha256_file(Path(project_path)), + runner_revision=runner_revision, + python={ + "abi": str(abi), + "implementation": platform.python_implementation().lower(), + "version": platform.python_version(), + }, + platform_tags=(sysconfig.get_platform(),), + distributions=tuple(distributions), + native_backends=native_backends or {}, + ) + + +def write_environment_descriptor(path: str | Path, descriptor: EnvironmentDescriptor) -> Path: + return _write_immutable_json(Path(path), descriptor.to_dict()) + + +def load_environment_descriptor(path: str | Path) -> EnvironmentDescriptor: + document = _read_object(Path(path), "environment descriptor") + if document.get("schema_version") != ENVIRONMENT_SCHEMA_VERSION: + raise ValueError("unsupported environment descriptor schema") + python = _required_object(document, "python") + backends = _required_object(document, "native_backends") + tags = _string_tuple(document, "platform_tags") + raw_distributions = document.get("distributions") + if not isinstance(raw_distributions, list): + raise TypeError("environment distributions must be a list") + distributions: list[tuple[str, str]] = [] + for row in raw_distributions: + if not isinstance(row, dict): + raise TypeError("environment distribution must be an object") + distributions.append((_required_string(row, "name"), _required_string(row, "version"))) + descriptor = EnvironmentDescriptor.build( + dependency_group=_required_string(document, "dependency_group"), + lock_sha256=_required_string(document, "lock_sha256"), + project_sha256=_required_string(document, "project_sha256"), + runner_revision=_required_string(document, "runner_revision"), + python={key: _required_string(python, key) for key in ("abi", "implementation", "version")}, + platform_tags=tags, + distributions=tuple(distributions), + native_backends={key: _required_string(backends, key) for key in backends}, + ) + if document.get("environment_id") != descriptor.environment_id: + raise ValueError("environment_id does not match descriptor content") + return descriptor + + +def _distribution_documents(distributions: tuple[tuple[str, str], ...]) -> list[dict[str, str]]: + return [{"name": name, "version": version} for name, version in distributions] + + +def _normalize_name(value: str) -> str: + if not value: + raise ValueError("distribution name must not be empty") + return re.sub(r"[-_.]+", "-", value).lower() + + +def _string_mapping(value: dict[str, str], field: str, *, allow_empty: bool = False) -> dict[str, str]: + invalid_entry = any(not isinstance(key, str) or not key or not val for key, val in value.items()) + if (not allow_empty and not value) or invalid_entry: + raise ValueError(f"{field} must contain non-empty string keys and values") + return dict(sorted(value.items())) + + +def _validate_digest(value: str, field: str) -> None: + if _DIGEST.fullmatch(value) is None: + raise ValueError(f"{field} must be a lowercase SHA-256 digest") + + +def _digest(payload: object) -> str: + canonical = json.dumps(payload, ensure_ascii=False, separators=(",", ":"), sort_keys=True) + return hashlib.sha256(canonical.encode()).hexdigest() + + +def _sha256_file(path: Path) -> str: + digest = hashlib.sha256() + try: + with path.open("rb") as file: + for block in iter(lambda: file.read(1024 * 1024), b""): + digest.update(block) + except OSError as exc: + raise ValueError(f"cannot hash environment input {path}: {exc}") from exc + return digest.hexdigest() + + +def _write_immutable_json(path: Path, payload: dict[str, object]) -> Path: + path.parent.mkdir(parents=True, exist_ok=True) + content = json.dumps(payload, ensure_ascii=False, indent=2, sort_keys=True) + "\n" + if path.exists(): + if path.read_text() != content: + raise ValueError(f"immutable descriptor already exists with different content: {path}") + return path + temporary: Path | None = None + try: + with tempfile.NamedTemporaryFile(mode="w", dir=path.parent, prefix=f".{path.name}.", delete=False) as file: + file.write(content) + file.flush() + os.fsync(file.fileno()) + temporary = Path(file.name) + try: + os.link(temporary, path) + except FileExistsError: + if path.read_text() != content: + raise ValueError(f"concurrent descriptor writer produced different content: {path}") from None + finally: + if temporary is not None: + temporary.unlink(missing_ok=True) + return path + + +def _read_object(path: Path, label: str) -> dict[str, Any]: + try: + value = json.loads(path.read_text()) + except (OSError, json.JSONDecodeError) as exc: + raise ValueError(f"cannot read {label} {path}: {exc}") from exc + if not isinstance(value, dict): + raise TypeError(f"{label} must be a JSON object") + return value + + +def _required_object(payload: dict[str, Any], key: str) -> dict[str, Any]: + value = payload.get(key) + if not isinstance(value, dict): + raise TypeError(f"field {key!r} must be an object") + return value + + +def _required_string(payload: dict[str, Any], key: str) -> str: + value = payload.get(key) + if not isinstance(value, str) or not value: + raise ValueError(f"field {key!r} must be a non-empty string") + return value + + +def _string_tuple(payload: dict[str, Any], key: str) -> tuple[str, ...]: + value = payload.get(key) + if not isinstance(value, list) or not value or not all(isinstance(item, str) and item for item in value): + raise ValueError(f"field {key!r} must be a non-empty string list") + return tuple(value) diff --git a/imread_benchmark/environments/provision.py b/imread_benchmark/environments/provision.py new file mode 100644 index 0000000..3db1782 --- /dev/null +++ b/imread_benchmark/environments/provision.py @@ -0,0 +1,360 @@ +from __future__ import annotations + +import hashlib +import json +import os +import platform +import re +import shutil +import subprocess +import sys +import sysconfig +import tempfile +from collections.abc import Callable +from dataclasses import dataclass, field +from pathlib import Path +from typing import Any + +from imread_benchmark.environments.descriptor import ( + ENVIRONMENT_SCHEMA_VERSION, + EnvironmentDescriptor, + load_environment_descriptor, + write_environment_descriptor, +) + +_REVISION = re.compile(r"^[0-9a-f]{40}(?:[0-9a-f]{24})?$") +_PROBE_SCRIPT = """ +import importlib.metadata +import json +import platform +import sys +import sysconfig + +distributions = [] +editable = [] +for distribution in importlib.metadata.distributions(): + name = distribution.metadata["Name"] + distributions.append([name, distribution.version]) + direct_url_text = distribution.read_text("direct_url.json") + if direct_url_text: + direct_url = json.loads(direct_url_text) + if direct_url.get("dir_info", {}).get("editable") is True: + editable.append(name) +print(json.dumps({ + "distributions": distributions, + "editable": editable, + "platform_tags": [sysconfig.get_platform()], + "python": { + "abi": str(sysconfig.get_config_var("SOABI") or sys.implementation.cache_tag or "unknown"), + "implementation": platform.python_implementation().lower(), + "version": platform.python_version(), + }, +}, sort_keys=True)) +""" + + +@dataclass(frozen=True, slots=True) +class EnvironmentRequest: + project_root: Path + cache_root: Path + dependency_group: str + runner_revision: str + python_executable: Path + native_backends: tuple[tuple[str, str], ...] = () + uv_command: tuple[str, ...] = ("uv",) + lock_sha256: str = field(init=False) + project_sha256: str = field(init=False) + python_identity: dict[str, str] = field(init=False) + platform_tags: tuple[str, ...] = field(init=False) + environment_key: str = field(init=False) + + def __post_init__(self) -> None: + project_root = self.project_root.resolve() + cache_root = self.cache_root.resolve() + python_executable = self.python_executable.resolve() + if not self.dependency_group: + raise ValueError("dependency_group must not be empty") + if _REVISION.fullmatch(self.runner_revision) is None: + raise ValueError("runner_revision must be a 40- or 64-character hexadecimal revision") + if not self.uv_command: + raise ValueError("uv_command must not be empty") + native_backends = _normalize_native_backends(self.native_backends) + lock_sha256 = _sha256_file(project_root / "uv.lock") + project_sha256 = _sha256_file(project_root / "pyproject.toml") + python_identity, platform_tags = _probe_python_identity(python_executable) + key_identity = { + "dependency_group": self.dependency_group, + "lock_sha256": lock_sha256, + "native_backends": dict(native_backends), + "platform_tags": platform_tags, + "project_sha256": project_sha256, + "python": python_identity, + "runner_revision": self.runner_revision, + "schema_version": ENVIRONMENT_SCHEMA_VERSION, + } + object.__setattr__(self, "project_root", project_root) + object.__setattr__(self, "cache_root", cache_root) + object.__setattr__(self, "python_executable", python_executable) + object.__setattr__(self, "native_backends", native_backends) + object.__setattr__(self, "lock_sha256", lock_sha256) + object.__setattr__(self, "project_sha256", project_sha256) + object.__setattr__(self, "python_identity", python_identity) + object.__setattr__(self, "platform_tags", platform_tags) + object.__setattr__(self, "environment_key", _digest(key_identity)) + + +@dataclass(frozen=True, slots=True) +class ProvisionedEnvironment: + root: Path + python_executable: Path + descriptor_path: Path + environment_key: str + environment_id: str + cache_hit: bool = field(compare=False) + + +EnvironmentProbe = Callable[[Path, str], EnvironmentDescriptor] + + +def provision_environment( + request: EnvironmentRequest, + *, + probe: EnvironmentProbe | None = None, +) -> ProvisionedEnvironment: + destination = request.cache_root / request.environment_key + if destination.exists(): + return _load_ready_environment(destination, request, cache_hit=True) + request.cache_root.mkdir(parents=True, exist_ok=True) + staging_parent = Path(tempfile.mkdtemp(prefix=f".{request.environment_key}.", dir=request.cache_root)) + staging = staging_parent / "environment" + try: + _run_frozen_sync(request, staging) + python_executable = _venv_python(staging) + if not python_executable.is_file(): + raise ValueError("uv sync did not create the expected environment Python executable") + descriptor = ( + probe(python_executable, request.environment_key) + if probe is not None + else _probe_installed_environment(python_executable, request) + ) + _validate_descriptor_for_request(descriptor, request) + write_environment_descriptor(staging / "environment.json", descriptor) + _write_ready_marker(staging, request, descriptor) + try: + staging.rename(destination) + except FileExistsError: + return _load_ready_environment(destination, request, cache_hit=True) + return _load_ready_environment(destination, request, cache_hit=False) + finally: + shutil.rmtree(staging_parent, ignore_errors=True) + + +def load_provisioned_environment( + root: str | Path, + request: EnvironmentRequest, + *, + cache_hit: bool, +) -> ProvisionedEnvironment: + return _load_ready_environment(Path(root).resolve(), request, cache_hit=cache_hit) + + +def _run_frozen_sync(request: EnvironmentRequest, destination: Path) -> None: + environment = { + **os.environ, + "UV_LINK_MODE": "copy", + "UV_PROJECT_ENVIRONMENT": str(destination), + } + command = ( + *request.uv_command, + "sync", + "--project", + str(request.project_root), + "--python", + str(request.python_executable), + "--no-group", + "dev", + "--frozen", + "--no-editable", + "--extra", + request.dependency_group, + ) + subprocess.run(command, check=True, env=environment) # noqa: S603 - explicit provisioner command + + +def _probe_installed_environment( + python_executable: Path, + request: EnvironmentRequest, +) -> EnvironmentDescriptor: + process = subprocess.run( # noqa: S603 - probes the newly-created immutable Python environment + (str(python_executable), "-c", _PROBE_SCRIPT), + check=True, + capture_output=True, + text=True, + ) + try: + document = json.loads(process.stdout) + except json.JSONDecodeError as exc: + raise ValueError("installed environment probe did not return JSON") from exc + if not isinstance(document, dict): + raise TypeError("installed environment probe must return an object") + editable = document.get("editable") + if not isinstance(editable, list) or not all(isinstance(name, str) for name in editable): + raise TypeError("installed environment probe returned invalid editable metadata") + if editable: + raise ValueError(f"editable distributions are forbidden: {', '.join(sorted(editable))}") + return EnvironmentDescriptor.build( + dependency_group=request.dependency_group, + lock_sha256=request.lock_sha256, + project_sha256=request.project_sha256, + runner_revision=request.runner_revision, + python=_probe_string_mapping(document, "python"), + platform_tags=_probe_string_tuple(document, "platform_tags"), + distributions=_probe_distributions(document), + native_backends=dict(request.native_backends), + ) + + +def _validate_descriptor_for_request(descriptor: EnvironmentDescriptor, request: EnvironmentRequest) -> None: + expected = ( + descriptor.dependency_group == request.dependency_group + and descriptor.lock_sha256 == request.lock_sha256 + and descriptor.project_sha256 == request.project_sha256 + and descriptor.runner_revision == request.runner_revision + and descriptor.native_backends == dict(request.native_backends) + and descriptor.python == request.python_identity + and descriptor.platform_tags == request.platform_tags + ) + if not expected: + raise ValueError("installed environment descriptor does not match provision request") + + +def _write_ready_marker( + root: Path, + request: EnvironmentRequest, + descriptor: EnvironmentDescriptor, +) -> None: + marker = { + "environment_id": descriptor.environment_id, + "environment_key": request.environment_key, + "schema_version": ENVIRONMENT_SCHEMA_VERSION, + "status": "ready", + } + (root / ".READY.json").write_text(json.dumps(marker, ensure_ascii=False, indent=2, sort_keys=True) + "\n") + + +def _load_ready_environment( + root: Path, + request: EnvironmentRequest, + *, + cache_hit: bool, +) -> ProvisionedEnvironment: + try: + marker = json.loads((root / ".READY.json").read_text()) + except (OSError, json.JSONDecodeError) as exc: + raise ValueError(f"environment cache entry is incomplete: {root}") from exc + if not isinstance(marker, dict): + raise TypeError("environment READY marker must be an object") + descriptor_path = root / "environment.json" + descriptor = load_environment_descriptor(descriptor_path) + if ( + marker.get("schema_version") != ENVIRONMENT_SCHEMA_VERSION + or marker.get("status") != "ready" + or marker.get("environment_key") != request.environment_key + or marker.get("environment_id") != descriptor.environment_id + ): + raise ValueError("environment READY marker does not match the cached descriptor") + _validate_descriptor_for_request(descriptor, request) + python_executable = _venv_python(root) + if not python_executable.is_file(): + raise ValueError("environment cache entry has no Python executable") + return ProvisionedEnvironment( + root=root, + python_executable=python_executable, + descriptor_path=descriptor_path, + environment_key=request.environment_key, + environment_id=descriptor.environment_id, + cache_hit=cache_hit, + ) + + +def _probe_python_identity(python_executable: Path) -> tuple[dict[str, str], tuple[str, ...]]: + if python_executable == Path(sys.executable).resolve(): + abi = sysconfig.get_config_var("SOABI") or sys.implementation.cache_tag or "unknown" + return ( + { + "abi": str(abi), + "implementation": platform.python_implementation().lower(), + "version": platform.python_version(), + }, + (sysconfig.get_platform(),), + ) + process = subprocess.run( # noqa: S603 - explicit Python interpreter selected by the caller + (str(python_executable), "-c", _PROBE_SCRIPT), + check=True, + capture_output=True, + text=True, + ) + document = json.loads(process.stdout) + if not isinstance(document, dict): + raise TypeError("Python identity probe must return an object") + return _probe_string_mapping(document, "python"), _probe_string_tuple(document, "platform_tags") + + +def _probe_distributions(document: dict[str, Any]) -> tuple[tuple[str, str], ...]: + value = document.get("distributions") + if not isinstance(value, list): + raise TypeError("environment probe distributions must be a list") + result: list[tuple[str, str]] = [] + for row in value: + if not isinstance(row, list) or len(row) != 2 or not all(isinstance(item, str) and item for item in row): + raise TypeError("environment probe distribution must be a name/version pair") + result.append((row[0], row[1])) + return tuple(result) + + +def _normalize_native_backends(values: tuple[tuple[str, str], ...]) -> tuple[tuple[str, str], ...]: + if any(len(row) != 2 or not row[0] or not row[1] for row in values): + raise ValueError("native_backends must contain non-empty name/version pairs") + names = [name for name, _ in values] + if len(names) != len(set(names)): + raise ValueError("native_backends contain duplicate names") + return tuple(sorted(values)) + + +def _probe_string_mapping(document: dict[str, Any], key: str) -> dict[str, str]: + value = document.get(key) + if not isinstance(value, dict) or any( + not isinstance(item_key, str) or not isinstance(item_value, str) or not item_value + for item_key, item_value in value.items() + ): + raise TypeError(f"environment probe field {key!r} must be a string mapping") + return value + + +def _probe_string_tuple(document: dict[str, Any], key: str) -> tuple[str, ...]: + value = document.get(key) + if not isinstance(value, list) or not value or not all(isinstance(item, str) and item for item in value): + raise TypeError(f"environment probe field {key!r} must be a non-empty string list") + return tuple(value) + + +def _venv_python(root: Path) -> Path: + bindir = "Scripts" if os.name == "nt" else "bin" + executable = "python.exe" if os.name == "nt" else "python" + return root / bindir / executable + + +def _sha256_file(path: Path) -> str: + digest = hashlib.sha256() + try: + with path.open("rb") as file: + for block in iter(lambda: file.read(1024 * 1024), b""): + digest.update(block) + except OSError as exc: + raise ValueError(f"cannot hash environment input {path}: {exc}") from exc + return digest.hexdigest() + + +def _digest(payload: object) -> str: + canonical = json.dumps(payload, ensure_ascii=False, separators=(",", ":"), sort_keys=True) + return hashlib.sha256(canonical.encode()).hexdigest() diff --git a/imread_benchmark/execution/__init__.py b/imread_benchmark/execution/__init__.py new file mode 100644 index 0000000..686316f --- /dev/null +++ b/imread_benchmark/execution/__init__.py @@ -0,0 +1,13 @@ +from imread_benchmark.execution.campaign import CampaignConfig, CampaignResult, run_campaign +from imread_benchmark.execution.coordinator import CoordinatorConfig, execute_run_specs +from imread_benchmark.execution.spec import RunIdentity, RunSpec + +__all__ = [ + "CampaignConfig", + "CampaignResult", + "CoordinatorConfig", + "RunIdentity", + "RunSpec", + "execute_run_specs", + "run_campaign", +] diff --git a/imread_benchmark/execution/campaign.py b/imread_benchmark/execution/campaign.py new file mode 100644 index 0000000..441241c --- /dev/null +++ b/imread_benchmark/execution/campaign.py @@ -0,0 +1,476 @@ +from __future__ import annotations + +import datetime as dt +import json +import os +import signal +import subprocess +import sys +import uuid +from dataclasses import dataclass +from pathlib import Path +from typing import TYPE_CHECKING + +from imread_benchmark.environments import load_environment_descriptor +from imread_benchmark.execution.coordinator import ( + AttemptResult, + AttemptStatus, + CoordinatorConfig, + RemoteCheckpoint, + execute_run_specs, +) +from imread_benchmark.execution.memory import estimate_peak_memory +from imread_benchmark.execution.spec import RunIdentity, RunSpec, write_run_spec +from imread_benchmark.plans import ( + ExperimentPlan, + PlanError, + RunConfiguration, + RunTemplate, + expand_experiment_plan, + load_experiment_plan, +) +from imread_benchmark.platforms import load_platform_descriptor +from imread_benchmark.support import ( + SupportAudit, + SupportSet, + build_common_support, + build_operational_support, + intersect_supported_items, + load_committed_support_audit, + write_support_set, +) +from imread_benchmark.support.spec import SupportAuditIdentity, SupportAuditSpec, write_support_audit_spec + +if TYPE_CHECKING: + from collections.abc import Sequence + + +class CampaignError(RuntimeError): + pass + + +@dataclass(frozen=True, slots=True) +class CampaignConfig: + plan_path: Path + package_descriptor: Path + environment_descriptor: Path + platform_descriptor: Path + artifact_root: Path + attempts_root: Path + runner_revision: str + worker_python: Path = Path(sys.executable) + remote: RemoteCheckpoint | None = None + + +@dataclass(frozen=True, slots=True) +class CampaignResult: + plan_id: str + environment_id: str + platform_id: str + run_results: tuple[AttemptResult, ...] + support_audit_count: int + support_set_ids: tuple[str, ...] + + @property + def complete(self) -> bool: + return all(result.status in {AttemptStatus.COMPLETED, AttemptStatus.SKIPPED} for result in self.run_results) + + def to_dict(self) -> dict[str, object]: + counts = {status.value: 0 for status in AttemptStatus} + for result in self.run_results: + counts[result.status.value] += 1 + return { + "complete": self.complete, + "environment_id": self.environment_id, + "plan_id": self.plan_id, + "platform_id": self.platform_id, + "run_counts": counts, + "run_keys": [result.run_key for result in self.run_results], + "schema_version": "2.0", + "support_audit_count": self.support_audit_count, + "support_set_ids": list(self.support_set_ids), + } + + +@dataclass(frozen=True, slots=True) +class _SupportContext: + process_context: str + multiprocessing_start_method: str | None + + +@dataclass(frozen=True, slots=True) +class _SupportExecution: + artifact_root: Path + attempts_root: Path + timeout_seconds: float + worker_python: Path + + +@dataclass(frozen=True, slots=True) +class _RunSpecContext: + plan: ExperimentPlan + environment_id: str + platform_id: str + runner_revision: str + environment_descriptor: Path + platform_descriptor: Path + artifact_root: Path + + +def run_campaign(config: CampaignConfig) -> CampaignResult: + plan = load_experiment_plan(config.plan_path, dataset_descriptor=config.package_descriptor) + templates = expand_experiment_plan(plan) + environment = load_environment_descriptor(config.environment_descriptor) + platform_descriptor = load_platform_descriptor(config.platform_descriptor) + if environment.runner_revision != config.runner_revision: + raise CampaignError("environment runner revision does not match campaign runner revision") + selected = tuple( + template + for template in templates + if _decoder_group(template.configuration.decoder_id) == environment.dependency_group + ) + if not selected: + raise CampaignError(f"plan has no decoders in environment group {environment.dependency_group!r}") + _validate_memory_budget(selected, plan=plan, platform_runtime=platform_descriptor.runtime) + for template in selected: + _validate_configuration_support(template.configuration, environment.distributions) + + artifact_root = config.artifact_root.resolve() + attempts_root = config.attempts_root.resolve() + artifact_root.mkdir(parents=True, exist_ok=True) + attempts_root.mkdir(parents=True, exist_ok=True) + audit_specs, audit_key_by_configuration = _support_specs( + selected, + plan=plan, + environment_id=environment.environment_id, + platform_id=platform_descriptor.platform_id, + runner_revision=config.runner_revision, + ) + audits = _execute_support_audits( + audit_specs, + artifact_root=artifact_root, + attempts_root=attempts_root, + timeout_seconds=float(plan.execution["run_timeout_seconds"]), + worker_python=config.worker_python, + ) + support_sets = _build_support_sets( + selected, + audits=audits, + audit_key_by_configuration=audit_key_by_configuration, + ordered_selection=plan.dataset.selection.item_ids, + artifact_root=artifact_root, + ) + run_spec_context = _RunSpecContext( + plan=plan, + environment_id=environment.environment_id, + platform_id=platform_descriptor.platform_id, + runner_revision=config.runner_revision, + environment_descriptor=config.environment_descriptor, + platform_descriptor=config.platform_descriptor, + artifact_root=artifact_root, + ) + specs = tuple( + _run_spec( + template, + context=run_spec_context, + support_set=support_sets[template.configuration.config_id], + ) + for template in selected + ) + _write_run_specs(artifact_root, specs) + results = execute_run_specs( + specs, + CoordinatorConfig( + artifact_root=artifact_root, + attempts_root=attempts_root / "runs", + timeout_seconds=float(plan.execution["run_timeout_seconds"]), + worker_command=(str(config.worker_python), "-m", "imread_benchmark.execution.worker"), + remote=config.remote, + ), + ) + first = selected[0] + result = CampaignResult( + plan_id=first.plan_id, + environment_id=environment.environment_id, + platform_id=platform_descriptor.platform_id, + run_results=results, + support_audit_count=len(audits), + support_set_ids=tuple(sorted({support.support_set_id for support in support_sets.values()})), + ) + _write_campaign_index(artifact_root, result) + return result + + +def _validate_memory_budget( + templates: Sequence[RunTemplate], + *, + plan: ExperimentPlan, + platform_runtime: dict[str, object], +) -> None: + memory_bytes = platform_runtime.get("memory_bytes") + if isinstance(memory_bytes, bool) or not isinstance(memory_bytes, int) or memory_bytes <= 0: + raise CampaignError("platform runtime must report positive memory_bytes for campaign preflight") + fraction = plan.execution.get("maximum_memory_fraction") + if isinstance(fraction, bool) or not isinstance(fraction, (int, float)): + raise CampaignError("plan has no valid maximum_memory_fraction") + selected_ids = set(plan.dataset.selection.item_ids) + raw_items = plan.dataset.manifest.get("items") + if not isinstance(raw_items, list) or not all(isinstance(item, dict) for item in raw_items): + raise CampaignError("dataset manifest has no valid items for memory preflight") + items = tuple(item for item in raw_items if item.get("item_id") in selected_ids) + limit = int(memory_bytes * float(fraction)) + configurations = {template.configuration.config_id: template.configuration for template in templates} + for configuration in configurations.values(): + try: + estimate = estimate_peak_memory(configuration, items) + except ValueError as exc: + raise CampaignError(f"cannot estimate memory for {configuration.config_id}: {exc}") from exc + if estimate.estimated_peak_bytes > limit: + raise CampaignError( + f"configuration {configuration.config_id} needs an estimated {estimate.estimated_peak_bytes} bytes, " + f"above the {limit}-byte campaign memory limit ({float(fraction):.3f} of platform RAM)", + ) + + +def _support_specs( + templates: Sequence[RunTemplate], + *, + plan: ExperimentPlan, + environment_id: str, + platform_id: str, + runner_revision: str, +) -> tuple[tuple[SupportAuditSpec, ...], dict[str, str]]: + specs: dict[str, SupportAuditSpec] = {} + keys: dict[str, str] = {} + for template in templates: + configuration = template.configuration + context = _support_context(configuration) + spec = SupportAuditSpec.build( + identity=SupportAuditIdentity( + decoder_id=configuration.decoder_id, + requested_threads=configuration.requested_threads, + package_id=plan.dataset.package_id, + workload_id=plan.dataset.workload_id, + manifest_id=plan.dataset.manifest_id, + selection_id=plan.dataset.selection.selection_id, + item_ids=plan.dataset.selection.item_ids, + process_context=context.process_context, + multiprocessing_start_method=context.multiprocessing_start_method, + environment_id=environment_id, + platform_id=platform_id, + runner_revision=runner_revision, + ), + package_descriptor=plan.dataset.descriptor_path, + ) + specs.setdefault(spec.audit_key, spec) + keys[configuration.config_id] = spec.audit_key + return tuple(sorted(specs.values(), key=lambda value: value.audit_key)), keys + + +def _execute_support_audits( + specs: Sequence[SupportAuditSpec], + *, + artifact_root: Path, + attempts_root: Path, + timeout_seconds: float, + worker_python: Path, +) -> dict[str, SupportAudit]: + support_root = artifact_root / "support" + spec_root = artifact_root / "specs" / "support" + audits: dict[str, SupportAudit] = {} + execution = _SupportExecution(artifact_root, attempts_root / "support", timeout_seconds, worker_python) + for spec in specs: + marker = support_root / "keys" / f"{spec.audit_key}.json" + if not marker.exists(): + spec_path = write_support_audit_spec(spec_root / f"{spec.audit_key}.json", spec) + _execute_support_audit_process( + spec_path, + spec.audit_key, + execution, + ) + try: + audits[spec.audit_key] = load_committed_support_audit(support_root, spec.audit_key) + except (OSError, TypeError, ValueError) as exc: + raise CampaignError(f"support audit {spec.audit_key} is incomplete or invalid: {exc}") from exc + return audits + + +def _execute_support_audit_process( + spec_path: Path, + audit_key: str, + execution: _SupportExecution, +) -> None: + attempt = execution.attempts_root / audit_key / f"{_utc_compact()}-{uuid.uuid4().hex}" + attempt.mkdir(parents=True) + command = ( + str(execution.worker_python), + "-m", + "imread_benchmark.support.worker", + "--audit-spec", + str(spec_path), + "--artifact-root", + str(execution.artifact_root), + ) + with (attempt / "stdout.log").open("wb") as stdout, (attempt / "stderr.log").open("wb") as stderr: + process = subprocess.Popen(command, stdout=stdout, stderr=stderr, start_new_session=True) # noqa: S603 + try: + exit_code = process.wait(timeout=execution.timeout_seconds) + except subprocess.TimeoutExpired as exc: + _terminate_process_group(process) + _write_support_status(attempt, audit_key, "timed-out", process.returncode) + raise CampaignError(f"support audit {audit_key} exceeded timeout") from exc + status = "completed" if exit_code == 0 else "failed" + _write_support_status(attempt, audit_key, status, exit_code) + if exit_code != 0: + raise CampaignError(f"support audit {audit_key} failed with exit code {exit_code}") + + +def _build_support_sets( + templates: Sequence[RunTemplate], + *, + audits: dict[str, SupportAudit], + audit_key_by_configuration: dict[str, str], + ordered_selection: Sequence[str], + artifact_root: Path, +) -> dict[str, SupportSet]: + configurations = {template.configuration.config_id: template.configuration for template in templates} + by_configuration: dict[str, SupportSet] = {} + common_groups: dict[_SupportContext, set[str]] = {} + for config_id, configuration in configurations.items(): + audit_key = audit_key_by_configuration[config_id] + if configuration.support_policy == "operational": + by_configuration[config_id] = build_operational_support(audits[audit_key]) + else: + common_groups.setdefault(_support_context(configuration), set()).add(audit_key) + global_common_ids: tuple[str, ...] | None = None + if common_groups: + common_audit_keys = sorted({key for keys in common_groups.values() for key in keys}) + global_common_ids = intersect_supported_items( + tuple(audits[key] for key in common_audit_keys), + ordered_selection=ordered_selection, + ) + for context, audit_keys in common_groups.items(): + if global_common_ids is None: + raise AssertionError("common support intersection was not computed") + support_set = build_common_support( + tuple(audits[key] for key in sorted(audit_keys)), + ordered_selection=global_common_ids, + ) + for config_id, configuration in configurations.items(): + if configuration.support_policy == "common" and _support_context(configuration) == context: + by_configuration[config_id] = support_set + for support_set in set(by_configuration.values()): + write_support_set(artifact_root / "support", support_set) + if set(by_configuration) != set(configurations): + raise AssertionError("campaign did not assign a support set to every configuration") + return by_configuration + + +def _run_spec( + template: RunTemplate, + *, + context: _RunSpecContext, + support_set: SupportSet, +) -> RunSpec: + configuration = template.configuration + return RunSpec.build( + identity=RunIdentity( + plan_id=template.plan_id, + platform_id=context.platform_id, + environment_id=context.environment_id, + runner_revision=context.runner_revision, + workload_id=context.plan.dataset.workload_id, + support_set_id=support_set.support_set_id, + support_item_ids=support_set.item_ids, + configuration=configuration, + repetition=template.repetition, + block_position=template.position, + ), + package_descriptor=context.plan.dataset.descriptor_path, + support_set_path=context.artifact_root / "support" / "sets" / f"{support_set.support_set_id}.json", + environment_descriptor=context.environment_descriptor, + platform_descriptor=context.platform_descriptor, + ) + + +def _write_run_specs(artifact_root: Path, specs: Sequence[RunSpec]) -> None: + root = artifact_root / "specs" / "runs" + for spec in specs: + write_run_spec(root / f"{spec.run_key}.json", spec) + + +def _support_context(configuration: RunConfiguration) -> _SupportContext: + if configuration.protocol_id == "loader-supply" and configuration.num_workers: + return _SupportContext("dataloader", configuration.multiprocessing_start_method) + return _SupportContext("main-process", None) + + +def _decoder_group(decoder_id: str) -> str: + from imread_benchmark.decoders import REGISTRY + + decoder = REGISTRY.get(decoder_id) + if decoder is None: + raise PlanError(f"unknown decoder in experiment plan: {decoder_id}") + return decoder.group + + +def _validate_configuration_support( + configuration: RunConfiguration, + distributions: tuple[tuple[str, str], ...], +) -> None: + from imread_benchmark.decoders import REGISTRY + + decoder = REGISTRY[configuration.decoder_id] + installed = {name for name, _ in distributions} + normalized_package = _normalize_package_name(decoder.package_name) + if normalized_package not in installed: + raise CampaignError( + f"environment does not contain package {normalized_package!r} for decoder {configuration.decoder_id!r}", + ) + if not decoder.runs_single_here(): + raise CampaignError(f"decoder {configuration.decoder_id!r} does not support this platform") + requires_workers = configuration.protocol_id == "loader-supply" and bool(configuration.num_workers) + if requires_workers and not decoder.runs_dataloader_here(): + raise CampaignError(f"decoder {configuration.decoder_id!r} does not support DataLoader workers here") + + +def _normalize_package_name(value: str) -> str: + return value.lower().replace("_", "-").replace(".", "-") + + +def _terminate_process_group(process: subprocess.Popen[bytes]) -> None: + if process.poll() is not None: + return + try: + os.killpg(process.pid, signal.SIGTERM) + except ProcessLookupError: + return + try: + process.wait(timeout=5) + except subprocess.TimeoutExpired: + try: + os.killpg(process.pid, signal.SIGKILL) + except ProcessLookupError: + return + process.wait(timeout=5) + + +def _write_support_status(attempt: Path, audit_key: str, status: str, exit_code: int | None) -> None: + document = { + "audit_key": audit_key, + "exit_code": exit_code, + "finished_at_utc": dt.datetime.now(dt.UTC).isoformat(), + "schema_version": "2.0", + "status": status, + } + (attempt / "status.json").write_text(json.dumps(document, ensure_ascii=False, indent=2, sort_keys=True) + "\n") + + +def _write_campaign_index(artifact_root: Path, result: CampaignResult) -> None: + path = artifact_root / "campaigns" / result.plan_id / result.platform_id / f"{result.environment_id}.json" + path.parent.mkdir(parents=True, exist_ok=True) + path.write_text(json.dumps(result.to_dict(), ensure_ascii=False, indent=2, sort_keys=True) + "\n") + + +def _utc_compact() -> str: + return dt.datetime.now(dt.UTC).strftime("%Y%m%dT%H%M%S.%fZ") diff --git a/imread_benchmark/execution/coordinator.py b/imread_benchmark/execution/coordinator.py new file mode 100644 index 0000000..7ef9f34 --- /dev/null +++ b/imread_benchmark/execution/coordinator.py @@ -0,0 +1,312 @@ +from __future__ import annotations + +import datetime as dt +import json +import os +import signal +import subprocess +import sys +import time +import uuid +from dataclasses import dataclass +from enum import StrEnum +from pathlib import Path +from typing import TYPE_CHECKING + +from imread_benchmark.artifacts import ( + BundleValidationError, + publish_run_bundle, + pull_committed_run, + validate_run_bundle, +) +from imread_benchmark.execution.spec import write_run_spec + +if TYPE_CHECKING: + from collections.abc import Sequence + + from imread_benchmark.datasets.materializer import ObjectStore + from imread_benchmark.execution.spec import RunSpec + + +class CoordinatorError(RuntimeError): + pass + + +class AttemptStatus(StrEnum): + COMPLETED = "completed" + FAILED = "failed" + SKIPPED = "skipped" + TIMED_OUT = "timed-out" + + +@dataclass(frozen=True, slots=True) +class AttemptResult: + run_key: str + status: AttemptStatus + exit_code: int | None + process_id: int | None + attempt_directory: Path | None + detail: str | None = None + + +@dataclass(frozen=True, slots=True) +class RemoteCheckpoint: + store: ObjectStore + prefix: str = "artifacts" + + +@dataclass(frozen=True, slots=True) +class CoordinatorConfig: + artifact_root: Path + attempts_root: Path + timeout_seconds: float + worker_command: tuple[str, ...] | None = None + remote: RemoteCheckpoint | None = None + + +@dataclass(frozen=True, slots=True) +class _AttemptContext: + directory: Path + spec: RunSpec + process_id: int + started_at: str + + +def execute_run_specs( + specs: Sequence[RunSpec], + config: CoordinatorConfig, +) -> tuple[AttemptResult, ...]: + if config.timeout_seconds <= 0: + raise ValueError("timeout_seconds must be positive") + if len({spec.run_key for spec in specs}) != len(specs): + raise CoordinatorError("run specifications contain duplicate run_key values") + artifact_path = config.artifact_root.resolve() + attempts_path = config.attempts_root.resolve() + artifact_path.mkdir(parents=True, exist_ok=True) + attempts_path.mkdir(parents=True, exist_ok=True) + command_prefix = config.worker_command or (sys.executable, "-m", "imread_benchmark.execution.worker") + if not command_prefix: + raise ValueError("worker_command must not be empty") + + results: list[AttemptResult] = [] + for spec in specs: + completed = artifact_path / "runs" / spec.run_key + if not completed.exists() and config.remote is not None: + pull_committed_run( + spec.run_key, + store=config.remote.store, + artifact_root=artifact_path, + prefix=config.remote.prefix, + ) + if completed.exists(): + _validate_existing(completed, spec.run_key) + results.append( + AttemptResult( + run_key=spec.run_key, + status=AttemptStatus.SKIPPED, + exit_code=None, + process_id=None, + attempt_directory=None, + detail="valid committed bundle already exists", + ), + ) + continue + result = _execute_one( + spec, + artifact_root=artifact_path, + attempts_root=attempts_path, + timeout_seconds=config.timeout_seconds, + command_prefix=command_prefix, + ) + if result.status is AttemptStatus.COMPLETED and config.remote is not None: + publish_run_bundle( + artifact_path / "runs" / spec.run_key, + store=config.remote.store, + prefix=config.remote.prefix, + ) + results.append(result) + return tuple(results) + + +def _validate_existing(path: Path, run_key: str) -> None: + try: + validate_run_bundle(path, expected_run_key=run_key) + except (BundleValidationError, OSError) as exc: + raise CoordinatorError(f"conflicting or incomplete bundle for run {run_key}: {exc}") from exc + + +def _execute_one( + spec: RunSpec, + *, + artifact_root: Path, + attempts_root: Path, + timeout_seconds: float, + command_prefix: tuple[str, ...], +) -> AttemptResult: + attempt_id = f"{_utc_compact()}-{uuid.uuid4().hex}" + attempt = attempts_root / spec.run_key / attempt_id + attempt.mkdir(parents=True) + spec_path = write_run_spec(attempt / "run-spec.json", spec) + command = ( + *command_prefix, + "--run-spec", + str(spec_path), + "--artifact-root", + str(artifact_root), + ) + started_at = _utc_now() + with (attempt / "stdout.log").open("wb") as stdout, (attempt / "stderr.log").open("wb") as stderr: + process = subprocess.Popen( # noqa: S603 - command is an explicit worker executable plus controlled arguments + command, + stdout=stdout, + stderr=stderr, + start_new_session=True, + ) + context = _AttemptContext( + directory=attempt, + spec=spec, + process_id=process.pid, + started_at=started_at, + ) + try: + exit_code = _wait_with_heartbeat(process, context, timeout_seconds) + except subprocess.TimeoutExpired: + _terminate_process_group(process) + return _finish_attempt( + context, + exit_code=process.returncode, + status=AttemptStatus.TIMED_OUT, + detail=f"worker exceeded timeout of {timeout_seconds:g} seconds", + ) + except KeyboardInterrupt: + _terminate_process_group(process) + _write_status( + context, + exit_code=process.returncode, + status="interrupted", + detail="coordinator interrupted", + ) + raise + + if exit_code != 0: + return _finish_attempt( + context, + exit_code=exit_code, + status=AttemptStatus.FAILED, + detail="worker exited abnormally", + ) + + bundle = artifact_root / "runs" / spec.run_key + try: + validate_run_bundle(bundle, expected_run_key=spec.run_key) + except (BundleValidationError, OSError) as exc: + return _finish_attempt( + context, + exit_code=exit_code, + status=AttemptStatus.FAILED, + detail=f"worker did not produce a valid bundle: {exc}", + ) + return _finish_attempt( + context, + exit_code=exit_code, + status=AttemptStatus.COMPLETED, + detail=None, + ) + + +def _wait_with_heartbeat( + process: subprocess.Popen[bytes], + context: _AttemptContext, + timeout_seconds: float, +) -> int: + deadline = time.monotonic() + timeout_seconds + _write_heartbeat(context) + while True: + remaining = deadline - time.monotonic() + if remaining <= 0: + raise subprocess.TimeoutExpired(process.args, timeout_seconds) + try: + return process.wait(timeout=min(5.0, remaining)) + except subprocess.TimeoutExpired: + _write_heartbeat(context) + + +def _write_heartbeat(context: _AttemptContext) -> None: + document = { + "process_id": context.process_id, + "run_key": context.spec.run_key, + "schema_version": "2.0", + "status": "running", + "updated_at_utc": _utc_now(), + } + path = context.directory / "heartbeat.json" + path.write_text(json.dumps(document, ensure_ascii=False, indent=2, sort_keys=True) + "\n") + + +def _finish_attempt( + context: _AttemptContext, + *, + exit_code: int | None, + status: AttemptStatus, + detail: str | None, +) -> AttemptResult: + _write_status( + context, + exit_code=exit_code, + status=status.value, + detail=detail, + ) + return AttemptResult( + run_key=context.spec.run_key, + status=status, + exit_code=exit_code, + process_id=context.process_id, + attempt_directory=context.directory, + detail=detail, + ) + + +def _terminate_process_group(process: subprocess.Popen[bytes]) -> None: + if process.poll() is not None: + return + try: + os.killpg(process.pid, signal.SIGTERM) + except ProcessLookupError: + return + try: + process.wait(timeout=5) + except subprocess.TimeoutExpired: + try: + os.killpg(process.pid, signal.SIGKILL) + except ProcessLookupError: + return + process.wait(timeout=5) + + +def _write_status( + context: _AttemptContext, + *, + exit_code: int | None, + status: str, + detail: str | None, +) -> None: + document = { + "detail": detail, + "exit_code": exit_code, + "finished_at_utc": _utc_now(), + "process_id": context.process_id, + "run_key": context.spec.run_key, + "schema_version": "2.0", + "started_at_utc": context.started_at, + "status": status, + } + status_path = context.directory / "status.json" + status_path.write_text(json.dumps(document, ensure_ascii=False, indent=2, sort_keys=True) + "\n") + + +def _utc_now() -> str: + return dt.datetime.now(dt.UTC).isoformat() + + +def _utc_compact() -> str: + return dt.datetime.now(dt.UTC).strftime("%Y%m%dT%H%M%S.%fZ") diff --git a/imread_benchmark/execution/loader_measurement.py b/imread_benchmark/execution/loader_measurement.py new file mode 100644 index 0000000..ac378d2 --- /dev/null +++ b/imread_benchmark/execution/loader_measurement.py @@ -0,0 +1,338 @@ +from __future__ import annotations + +import datetime as dt +import gc +import multiprocessing +import os +import queue +import time +from dataclasses import dataclass +from typing import TYPE_CHECKING, Any + +from imread_benchmark.artifacts import RunSample +from imread_benchmark.contracts import OutputContract, validate_output +from imread_benchmark.execution.measurement import MeasurementError, MeasurementResult, configure_decoder + +if TYPE_CHECKING: + from collections.abc import Sequence + + import numpy as np + + from imread_benchmark.datasets.package import ResidentItem + from imread_benchmark.plans import RunConfiguration + + +@dataclass(frozen=True, slots=True) +class _Traversal: + item_count: int + decoded_pixels: int + + +@dataclass(frozen=True, slots=True) +class _DecoderWorkerConfig: + decoder_id: str + requested_threads: int | None + logical_repeat_factor: int + multiprocessing_start_method: str + + +@dataclass(frozen=True, slots=True) +class _LoaderRuntime: + loader: Any + handshake_queue: Any + handshake_generation: Any + multiprocessing_start_method: str + expected_processes: int + logical_items: int + + +@dataclass(frozen=True, slots=True) +class _Preparation: + validation: _Traversal + validation_handshakes: list[dict[str, object]] + persistent_workers_reused: bool + events: list[dict[str, object]] + + +@dataclass(slots=True) +class _LocalGeneration: + value: int = 0 + + +class ResidentLoaderDataset: + def __init__( + self, + images_bytes: tuple[bytes, ...], + *, + worker_config: _DecoderWorkerConfig, + handshake_queue: Any, + handshake_generation: Any, + ) -> None: + self.images_bytes = images_bytes + self.worker_config = worker_config + self.handshake_queue = handshake_queue + self.handshake_generation = handshake_generation + self._decoder: Any = None + self._effective_threads: int | None = None + self._reported_generation = -1 + + def __len__(self) -> int: + return len(self.images_bytes) * self.worker_config.logical_repeat_factor + + def __getitem__(self, index: int) -> np.ndarray: + decoder = self._get_decoder() + generation = int(self.handshake_generation.value) + if self._reported_generation != generation: + self.handshake_queue.put( + { + "effective_threads": self._effective_threads, + "generation": generation, + "multiprocessing_start_method": self.worker_config.multiprocessing_start_method, + "process_id": os.getpid(), + }, + ) + self._reported_generation = generation + output = decoder.decode(self.images_bytes[index % len(self.images_bytes)]) + validate_output(output, OutputContract.normalized_rgb()) + return output + + def _get_decoder(self) -> Any: + if self._decoder is None: + from imread_benchmark.decoders import REGISTRY + + decoder_class = REGISTRY.get(self.worker_config.decoder_id) + if decoder_class is None: + raise MeasurementError(f"unknown decoder {self.worker_config.decoder_id!r} in DataLoader worker") + self._decoder = decoder_class() + self._effective_threads = configure_decoder(self._decoder, self.worker_config.requested_threads) + return self._decoder + + +def pass_through_collate(batch: list[np.ndarray]) -> list[np.ndarray]: + return batch + + +def run_loader_supply_measurement( + items: Sequence[ResidentItem], + configuration: RunConfiguration, +) -> MeasurementResult: + _validate_configuration(items, configuration) + runtime = _build_loader_runtime(items, configuration) + preparation = _prepare_loader(runtime, configuration) + samples = _measure_loader(runtime, configuration) + preparation.events.append({"event": "measurement_complete", "sample_count": len(samples)}) + resident_bytes = sum(item.compressed_bytes for item in items) + _shutdown_loader(runtime.loader) + close_queue = getattr(runtime.handshake_queue, "close", None) + if callable(close_queue): + close_queue() + join_queue = getattr(runtime.handshake_queue, "join_thread", None) + if callable(join_queue): + join_queue() + return MeasurementResult( + samples=tuple(samples), + runtime={ + "multiprocessing_start_method": runtime.multiprocessing_start_method, + "persistent_workers_reused": preparation.persistent_workers_reused, + "worker_handshakes": preparation.validation_handshakes, + }, + summary_fields={ + "batch_size": configuration.batch_size, + "compressed_bytes_per_pass": resident_bytes * configuration.logical_repeat_factor, + "decoded_pixels_per_pass": preparation.validation.decoded_pixels, + "logical_decodes_per_pass": runtime.logical_items, + "logical_repeat_factor": configuration.logical_repeat_factor, + "num_unique_images": len(items), + "num_workers": configuration.num_workers, + "resident_compressed_bytes": resident_bytes, + "timed_transport": "in-process" if configuration.num_workers == 0 else "dataloader-process-queue", + }, + events=tuple(preparation.events), + ) + + +def _validate_configuration(items: Sequence[ResidentItem], configuration: RunConfiguration) -> None: + if configuration.protocol_id != "loader-supply": + raise MeasurementError(f"loader-supply runner received protocol {configuration.protocol_id!r}") + if not items: + raise MeasurementError("loader-supply requires at least one resident item") + if configuration.num_workers is None or configuration.batch_size is None: + raise MeasurementError("loader-supply configuration is missing worker or batch settings") + + +def _build_loader_runtime( + items: Sequence[ResidentItem], + configuration: RunConfiguration, +) -> _LoaderRuntime: + try: + from torch.utils.data import DataLoader + except ImportError as exc: + raise MeasurementError("loader-supply requires PyTorch") from exc + num_workers = configuration.num_workers + if num_workers is None: + raise MeasurementError("loader-supply configuration is missing num_workers") + handshake_queue: Any + handshake_generation: Any + if num_workers == 0: + context = None + handshake_queue = queue.Queue() + handshake_generation = _LocalGeneration() + start_method = "in-process" + else: + try: + context = multiprocessing.get_context(configuration.multiprocessing_start_method) + except ValueError as exc: + raise MeasurementError( + f"multiprocessing start method {configuration.multiprocessing_start_method!r} is unavailable", + ) from exc + handshake_queue = context.Queue() + handshake_generation = context.Value("i", 0) + start_method = context.get_start_method() + dataset = ResidentLoaderDataset( + tuple(item.data for item in items), + worker_config=_DecoderWorkerConfig( + decoder_id=configuration.decoder_id, + requested_threads=configuration.requested_threads, + logical_repeat_factor=configuration.logical_repeat_factor, + multiprocessing_start_method=start_method, + ), + handshake_queue=handshake_queue, + handshake_generation=handshake_generation, + ) + loader_kwargs: dict[str, Any] = { + "batch_size": configuration.batch_size, + "collate_fn": pass_through_collate, + "dataset": dataset, + "num_workers": num_workers, + "persistent_workers": configuration.persistent_workers, + } + if num_workers > 0: + if context is None: + raise AssertionError("worker process context was not created") + loader_kwargs["multiprocessing_context"] = context + loader_kwargs["prefetch_factor"] = configuration.prefetch_factor + return _LoaderRuntime( + loader=DataLoader(**loader_kwargs), + handshake_queue=handshake_queue, + handshake_generation=handshake_generation, + multiprocessing_start_method=start_method, + expected_processes=max(1, num_workers), + logical_items=len(dataset), + ) + + +def _prepare_loader(runtime: _LoaderRuntime, configuration: RunConfiguration) -> _Preparation: + validation_started = time.perf_counter() + validation = _consume_loader(runtime.loader) + _require_item_count("validation", validation.item_count, runtime.logical_items) + validation_handshakes = _receive_handshakes( + runtime.handshake_queue, + runtime.expected_processes, + generation=0, + ) + events = [_phase_event("validation", validation_started, item_count=validation.item_count)] + + warmup_started = time.perf_counter() + persistent_workers_reused = configuration.num_workers == 0 + if configuration.warmup_passes > 0: + runtime.handshake_generation.value = 1 + for _ in range(configuration.warmup_passes): + warmup = _consume_loader(runtime.loader) + _require_item_count("warmup", warmup.item_count, runtime.logical_items) + warmup_handshakes = _receive_handshakes( + runtime.handshake_queue, + runtime.expected_processes, + generation=1, + ) + persistent_workers_reused = _process_ids(validation_handshakes) == _process_ids(warmup_handshakes) + events.append( + _phase_event( + "warmup", + warmup_started, + passes=configuration.warmup_passes, + persistent_workers_reused=persistent_workers_reused, + ), + ) + if configuration.num_workers and not persistent_workers_reused: + raise MeasurementError("DataLoader worker processes were not reused between validation and warmup") + return _Preparation(validation, validation_handshakes, persistent_workers_reused, events) + + +def _measure_loader(runtime: _LoaderRuntime, configuration: RunConfiguration) -> list[RunSample]: + samples: list[RunSample] = [] + for sample_index in range(configuration.timed_passes_per_run): + gc.collect() + gc.disable() + try: + started_at_utc = dt.datetime.now(dt.UTC).isoformat() + started = time.perf_counter() + traversal = _consume_loader(runtime.loader) + elapsed = time.perf_counter() - started + finally: + gc.enable() + _require_item_count("timed pass", traversal.item_count, runtime.logical_items) + if elapsed < configuration.minimum_timed_seconds: + raise MeasurementError( + f"timed pass {sample_index} lasted {elapsed:.9f}s, below the pinned minimum " + f"{configuration.minimum_timed_seconds:.9f}s", + ) + samples.append(RunSample(sample_index, elapsed, runtime.logical_items, started_at_utc)) + return samples + + +def _require_item_count(phase: str, observed: int, expected: int) -> None: + if observed != expected: + raise MeasurementError(f"{phase} yielded {observed} items, expected {expected}") + + +def _shutdown_loader(loader: Any) -> None: + iterator = getattr(loader, "_iterator", None) + shutdown = getattr(iterator, "_shutdown_workers", None) + if callable(shutdown): + shutdown() + + +def _consume_loader(loader: Any) -> _Traversal: + item_count = 0 + decoded_pixels = 0 + for batch in loader: + item_count += len(batch) + decoded_pixels += sum(int(output.shape[0]) * int(output.shape[1]) for output in batch) + return _Traversal(item_count=item_count, decoded_pixels=decoded_pixels) + + +def _receive_handshakes(handshake_queue: Any, count: int, *, generation: int) -> list[dict[str, object]]: + rows: list[dict[str, object]] = [] + try: + for _ in range(count): + row = handshake_queue.get(timeout=10) + if not isinstance(row, dict) or row.get("generation") != generation: + raise MeasurementError("DataLoader worker returned an invalid handshake") + rows.append(row) + except queue.Empty as exc: + raise MeasurementError(f"received {len(rows)} of {count} expected DataLoader worker handshakes") from exc + if len(_process_ids(rows)) != count: + raise MeasurementError("DataLoader handshakes do not represent the expected number of processes") + return sorted(rows, key=_handshake_process_id) + + +def _process_ids(handshakes: Sequence[dict[str, object]]) -> set[int]: + process_ids = {row.get("process_id") for row in handshakes} + if any(isinstance(value, bool) or not isinstance(value, int) for value in process_ids): + raise MeasurementError("DataLoader worker handshake has an invalid process ID") + return {value for value in process_ids if isinstance(value, int)} + + +def _handshake_process_id(handshake: dict[str, object]) -> int: + process_id = handshake.get("process_id") + if isinstance(process_id, bool) or not isinstance(process_id, int): + raise MeasurementError("DataLoader worker handshake has an invalid process ID") + return process_id + + +def _phase_event(event: str, started: float, **details: object) -> dict[str, object]: + return { + "duration_seconds": time.perf_counter() - started, + "event": event, + **details, + } diff --git a/imread_benchmark/execution/measurement.py b/imread_benchmark/execution/measurement.py new file mode 100644 index 0000000..6137fc8 --- /dev/null +++ b/imread_benchmark/execution/measurement.py @@ -0,0 +1,143 @@ +from __future__ import annotations + +import datetime as dt +import gc +import time +from dataclasses import dataclass +from typing import TYPE_CHECKING + +from imread_benchmark.artifacts import RunSample +from imread_benchmark.contracts import OutputContract, validate_output + +if TYPE_CHECKING: + from collections.abc import Sequence + + from imread_benchmark.datasets.package import ResidentItem + from imread_benchmark.decoders import BaseDecoder + from imread_benchmark.plans import RunConfiguration + + +class MeasurementError(RuntimeError): + pass + + +@dataclass(frozen=True, slots=True) +class MeasurementResult: + samples: tuple[RunSample, ...] + runtime: dict[str, object] + summary_fields: dict[str, object] + events: tuple[dict[str, object], ...] + + +def configure_decoder(decoder: BaseDecoder, requested_threads: int | None) -> int: + from imread_benchmark.decoders import BaseDecoder + + if requested_threads is not None: + if type(decoder).set_num_threads is BaseDecoder.set_num_threads: + raise MeasurementError(f"decoder {decoder.name!r} does not expose thread control") + decoder.set_num_threads(requested_threads) + effective = decoder.get_num_threads() + if effective <= 0: + raise MeasurementError(f"decoder {decoder.name!r} reported invalid effective thread count {effective}") + if requested_threads is not None and effective != requested_threads: + raise MeasurementError( + f"decoder {decoder.name!r} reported {effective} effective threads after requesting {requested_threads}", + ) + return effective + + +def run_decode_memory_measurement( + decoder: BaseDecoder, + items: Sequence[ResidentItem], + configuration: RunConfiguration, + *, + effective_threads: int, +) -> MeasurementResult: + if configuration.protocol_id != "decode-memory": + raise MeasurementError(f"decode-memory runner received protocol {configuration.protocol_id!r}") + if not items: + raise MeasurementError("decode-memory requires at least one resident item") + contract = OutputContract.normalized_rgb() + events: list[dict[str, object]] = [] + + validation_started = time.perf_counter() + decoded_pixels = 0 + for item in items: + try: + output = decoder.decode(item.data) + validate_output(output, contract) + except Exception as exc: + raise MeasurementError( + f"pinned support item {item.item_id!r} failed validation: {type(exc).__name__}: {exc}", + ) from exc + decoded_pixels += int(output.shape[0]) * int(output.shape[1]) + events.append(_phase_event("validation", validation_started, item_count=len(items))) + + warmup_started = time.perf_counter() + for _ in range(configuration.warmup_passes): + _decode_pass(decoder, items, configuration.logical_repeat_factor) + events.append(_phase_event("warmup", warmup_started, passes=configuration.warmup_passes)) + + logical_items = len(items) * configuration.logical_repeat_factor + samples: list[RunSample] = [] + for sample_index in range(configuration.timed_passes_per_run): + gc.collect() + gc.disable() + try: + started_at_utc = dt.datetime.now(dt.UTC).isoformat() + started = time.perf_counter() + _decode_pass(decoder, items, configuration.logical_repeat_factor) + elapsed = time.perf_counter() - started + finally: + gc.enable() + if elapsed < configuration.minimum_timed_seconds: + raise MeasurementError( + f"timed pass {sample_index} lasted {elapsed:.9f}s, below the pinned minimum " + f"{configuration.minimum_timed_seconds:.9f}s", + ) + samples.append( + RunSample( + sample_index=sample_index, + elapsed_seconds=elapsed, + items_processed=logical_items, + started_at_utc=started_at_utc, + ), + ) + events.append( + { + "event": "measurement_complete", + "sample_count": len(samples), + }, + ) + resident_bytes = sum(item.compressed_bytes for item in items) + return MeasurementResult( + samples=tuple(samples), + runtime={ + "effective_threads": effective_threads, + "requested_threads": configuration.requested_threads, + }, + summary_fields={ + "compressed_bytes_per_pass": resident_bytes * configuration.logical_repeat_factor, + "decoded_pixels_per_pass": decoded_pixels * configuration.logical_repeat_factor, + "logical_decodes_per_pass": logical_items, + "logical_repeat_factor": configuration.logical_repeat_factor, + "num_unique_images": len(items), + "resident_compressed_bytes": resident_bytes, + "timed_input_source": "resident-tar-bytes", + }, + events=tuple(events), + ) + + +def _decode_pass(decoder: BaseDecoder, items: Sequence[ResidentItem], repeats: int) -> None: + for _ in range(repeats): + for item in items: + decoder.decode(item.data) + + +def _phase_event(event: str, started: float, **details: object) -> dict[str, object]: + return { + "duration_seconds": time.perf_counter() - started, + "event": event, + **details, + } diff --git a/imread_benchmark/execution/memory.py b/imread_benchmark/execution/memory.py new file mode 100644 index 0000000..9f5bea3 --- /dev/null +++ b/imread_benchmark/execution/memory.py @@ -0,0 +1,74 @@ +from __future__ import annotations + +from dataclasses import dataclass +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from collections.abc import Mapping, Sequence + + from imread_benchmark.plans import RunConfiguration + + +@dataclass(frozen=True, slots=True) +class MemoryEstimate: + resident_compressed_bytes: int + resident_replica_count: int + decoded_inflight_bytes: int + estimated_peak_bytes: int + + def to_dict(self) -> dict[str, int]: + return { + "decoded_inflight_bytes": self.decoded_inflight_bytes, + "estimated_peak_bytes": self.estimated_peak_bytes, + "resident_compressed_bytes": self.resident_compressed_bytes, + "resident_replica_count": self.resident_replica_count, + } + + +def estimate_peak_memory( + configuration: RunConfiguration, + items: Sequence[Mapping[str, object]], +) -> MemoryEstimate: + if not items: + raise ValueError("memory estimate requires at least one manifest item") + compressed = sum(_positive_int(item, "compressed_bytes") for item in items) + max_decoded = max(_positive_int(item, "width") * _positive_int(item, "height") * 3 for item in items) + replicas = _resident_replicas(configuration) + decoded = _decoded_inflight(configuration, max_decoded) + return MemoryEstimate( + resident_compressed_bytes=compressed, + resident_replica_count=replicas, + decoded_inflight_bytes=decoded, + estimated_peak_bytes=compressed * replicas + decoded, + ) + + +def _resident_replicas(configuration: RunConfiguration) -> int: + if configuration.protocol_id != "loader-supply" or not configuration.num_workers: + return 1 + if configuration.multiprocessing_start_method == "fork": + return 1 + return configuration.num_workers + 1 + + +def _decoded_inflight(configuration: RunConfiguration, max_decoded: int) -> int: + if configuration.protocol_id == "decode-memory": + return max_decoded + if configuration.batch_size is None or configuration.num_workers is None: + raise ValueError("loader memory estimate requires batch_size and num_workers") + if configuration.num_workers == 0: + return (configuration.batch_size + 1) * max_decoded + if configuration.prefetch_factor is None: + raise ValueError("loader worker memory estimate requires prefetch_factor") + queued_batches = configuration.num_workers * configuration.prefetch_factor + 1 + queued_decoded = queued_batches * configuration.batch_size * max_decoded + worker_temporaries = configuration.num_workers * max_decoded + return queued_decoded + worker_temporaries + + +def _positive_int(item: Mapping[str, object], key: str) -> int: + value = item.get(key) + if isinstance(value, bool) or not isinstance(value, int) or value <= 0: + item_id = item.get("item_id", "") + raise ValueError(f"manifest item {item_id!r} has invalid {key}") + return value diff --git a/imread_benchmark/execution/spec.py b/imread_benchmark/execution/spec.py new file mode 100644 index 0000000..e24e313 --- /dev/null +++ b/imread_benchmark/execution/spec.py @@ -0,0 +1,217 @@ +from __future__ import annotations + +import hashlib +import json +import re +import tempfile +from dataclasses import asdict, dataclass +from pathlib import Path +from typing import Any + +from imread_benchmark.plans import RunConfiguration + +RUN_SPEC_SCHEMA_VERSION = "2.0" +_DIGEST = re.compile(r"^[0-9a-f]{64}$") +_REVISION = re.compile(r"^[0-9a-f]{40}(?:[0-9a-f]{24})?$") +_SAFE_ID = re.compile(r"^[A-Za-z0-9][A-Za-z0-9._-]*$") + + +class RunSpecError(ValueError): + pass + + +@dataclass(frozen=True, slots=True) +class RunIdentity: + plan_id: str + platform_id: str + environment_id: str + runner_revision: str + workload_id: str + support_set_id: str + support_item_ids: tuple[str, ...] + configuration: RunConfiguration + repetition: int + block_position: int + + def __post_init__(self) -> None: + for field_name in ("plan_id", "platform_id", "environment_id", "support_set_id"): + _validate_digest(getattr(self, field_name), field=field_name) + if _REVISION.fullmatch(self.runner_revision) is None: + raise RunSpecError("runner_revision must be a 40- or 64-character lowercase hexadecimal revision") + if _SAFE_ID.fullmatch(self.workload_id) is None or self.workload_id in {".", ".."}: + raise RunSpecError("workload_id must be a safe identifier") + if not self.support_item_ids or len(set(self.support_item_ids)) != len(self.support_item_ids): + raise RunSpecError("support_item_ids must be non-empty and unique") + if any(not item_id for item_id in self.support_item_ids): + raise RunSpecError("support_item_ids must not contain empty identifiers") + if self.repetition < 0 or self.block_position < 0: + raise RunSpecError("repetition and block_position must be non-negative") + + def to_dict(self) -> dict[str, object]: + return { + "block_position": self.block_position, + "configuration": asdict(self.configuration), + "environment_id": self.environment_id, + "package_id": self.configuration.package_id, + "plan_id": self.plan_id, + "platform_id": self.platform_id, + "repetition": self.repetition, + "runner_revision": self.runner_revision, + "schema_version": RUN_SPEC_SCHEMA_VERSION, + "support_item_ids": list(self.support_item_ids), + "support_set_id": self.support_set_id, + "workload_manifest_id": self.configuration.manifest_id, + "workload_id": self.workload_id, + } + + +@dataclass(frozen=True, slots=True) +class RunSpec: + run_key: str + identity: RunIdentity + package_descriptor: Path + support_set_path: Path + environment_descriptor: Path + platform_descriptor: Path + schema_version: str = RUN_SPEC_SCHEMA_VERSION + + @classmethod + def build( + cls, + *, + identity: RunIdentity, + package_descriptor: str | Path, + support_set_path: str | Path, + environment_descriptor: str | Path, + platform_descriptor: str | Path, + ) -> RunSpec: + return cls( + run_key=compute_run_key(identity), + identity=identity, + package_descriptor=Path(package_descriptor).resolve(), + support_set_path=Path(support_set_path).resolve(), + environment_descriptor=Path(environment_descriptor).resolve(), + platform_descriptor=Path(platform_descriptor).resolve(), + ) + + @classmethod + def from_dict(cls, document: dict[str, Any]) -> RunSpec: + if document.get("schema_version") != RUN_SPEC_SCHEMA_VERSION: + raise RunSpecError(f"unsupported run spec schema: {document.get('schema_version')!r}") + configuration_document = _required_object(document, "configuration") + try: + configuration = RunConfiguration(**configuration_document) + except (TypeError, ValueError) as exc: + raise RunSpecError(f"invalid run configuration: {exc}") from exc + raw_items = document.get("support_item_ids") + if not isinstance(raw_items, list) or not all(isinstance(item, str) for item in raw_items): + raise RunSpecError("support_item_ids must be a string list") + identity = RunIdentity( + plan_id=_required_string(document, "plan_id"), + platform_id=_required_string(document, "platform_id"), + environment_id=_required_string(document, "environment_id"), + runner_revision=_required_string(document, "runner_revision"), + workload_id=_required_string(document, "workload_id"), + support_set_id=_required_string(document, "support_set_id"), + support_item_ids=tuple(raw_items), + configuration=configuration, + repetition=_required_int(document, "repetition"), + block_position=_required_int(document, "block_position"), + ) + spec = cls.build( + identity=identity, + package_descriptor=_required_string(document, "package_descriptor"), + support_set_path=_required_string(document, "support_set_path"), + environment_descriptor=_required_string(document, "environment_descriptor"), + platform_descriptor=_required_string(document, "platform_descriptor"), + ) + if document.get("run_key") != spec.run_key: + raise RunSpecError("run_key does not match run spec identity") + return spec + + def __post_init__(self) -> None: + _validate_digest(self.run_key, field="run_key") + if self.schema_version != RUN_SPEC_SCHEMA_VERSION: + raise RunSpecError(f"unsupported run spec schema: {self.schema_version!r}") + + def to_dict(self) -> dict[str, object]: + return { + **self.identity.to_dict(), + "package_descriptor": str(self.package_descriptor), + "environment_descriptor": str(self.environment_descriptor), + "platform_descriptor": str(self.platform_descriptor), + "run_key": self.run_key, + "schema_version": self.schema_version, + "support_set_path": str(self.support_set_path), + } + + +def write_run_spec(path: str | Path, spec: RunSpec) -> Path: + destination = Path(path) + destination.parent.mkdir(parents=True, exist_ok=True) + content = json.dumps(spec.to_dict(), ensure_ascii=False, indent=2, sort_keys=True) + "\n" + if destination.exists(): + if destination.read_text() != content: + raise RunSpecError(f"existing run spec has different content: {destination}") + return destination + temporary: Path | None = None + try: + with tempfile.NamedTemporaryFile( + mode="w", + dir=destination.parent, + prefix=f".{destination.name}.", + delete=False, + ) as file: + file.write(content) + temporary = Path(file.name) + temporary.rename(destination) + finally: + if temporary is not None: + temporary.unlink(missing_ok=True) + return destination + + +def load_run_spec(path: str | Path) -> RunSpec: + source = Path(path) + try: + value = json.loads(source.read_text()) + except (OSError, json.JSONDecodeError) as exc: + raise RunSpecError(f"cannot read run spec {source}: {exc}") from exc + if not isinstance(value, dict): + raise RunSpecError("run spec must be a JSON object") + return RunSpec.from_dict(value) + + +def compute_run_key(identity: RunIdentity) -> str: + return _digest(identity.to_dict()) + + +def _required_object(payload: dict[str, Any], key: str) -> dict[str, Any]: + value = payload.get(key) + if not isinstance(value, dict): + raise RunSpecError(f"field {key!r} must be an object") + return value + + +def _required_string(payload: dict[str, Any], key: str) -> str: + value = payload.get(key) + if not isinstance(value, str) or not value: + raise RunSpecError(f"field {key!r} must be a non-empty string") + return value + + +def _required_int(payload: dict[str, Any], key: str) -> int: + value = payload.get(key) + if isinstance(value, bool) or not isinstance(value, int): + raise RunSpecError(f"field {key!r} must be an integer") + return value + + +def _validate_digest(value: str, *, field: str) -> None: + if _DIGEST.fullmatch(value) is None: + raise RunSpecError(f"{field} must be a lowercase SHA-256 digest") + + +def _digest(payload: object) -> str: + canonical = json.dumps(payload, ensure_ascii=False, separators=(",", ":"), sort_keys=True) + return hashlib.sha256(canonical.encode()).hexdigest() diff --git a/imread_benchmark/execution/worker.py b/imread_benchmark/execution/worker.py new file mode 100644 index 0000000..87fb527 --- /dev/null +++ b/imread_benchmark/execution/worker.py @@ -0,0 +1,200 @@ +from __future__ import annotations + +import argparse +import os +import platform +import sys +from dataclasses import asdict, dataclass +from pathlib import Path +from typing import TYPE_CHECKING + +from imread_benchmark.artifacts import BundleData, write_run_bundle +from imread_benchmark.datasets.package import DatasetPackageError, open_dataset_package +from imread_benchmark.environments import EnvironmentDescriptor, load_environment_descriptor +from imread_benchmark.execution.measurement import MeasurementError, configure_decoder, run_decode_memory_measurement +from imread_benchmark.execution.spec import RunSpecError, load_run_spec +from imread_benchmark.platforms import PlatformDescriptor, load_platform_descriptor +from imread_benchmark.support import load_support_set + +if TYPE_CHECKING: + from imread_benchmark.datasets.package import ResidentItem + from imread_benchmark.decoders import BaseDecoder + from imread_benchmark.execution.measurement import MeasurementResult + from imread_benchmark.execution.spec import RunSpec + from imread_benchmark.plans import RunConfiguration + from imread_benchmark.support import SupportSet + + +@dataclass(frozen=True, slots=True) +class _BundleContext: + runtime: dict[str, object] + environment: dict[str, object] + platform: dict[str, object] + + +def run_worker(run_spec_path: str | Path, artifact_root: str | Path) -> Path: + spec = load_run_spec(run_spec_path) + configuration = spec.identity.configuration + environment, platform_descriptor = _load_provenance(spec) + preimport_runtime = { + "machine": platform.machine(), + "process_id": os.getpid(), + "python_executable": sys.executable, + "python_version": platform.python_version(), + "system": platform.system(), + } + + selected_items = _load_selected_items(spec) + support_set = _load_pinned_support_set(spec) + if configuration.protocol_id == "decode-memory": + decoder, effective_threads = _load_decoder(configuration) + measurement = run_decode_memory_measurement( + decoder, + selected_items, + configuration, + effective_threads=effective_threads, + ) + elif configuration.protocol_id == "loader-supply": + from imread_benchmark.execution.loader_measurement import run_loader_supply_measurement + + measurement = run_loader_supply_measurement(selected_items, configuration) + else: + raise MeasurementError(f"unsupported worker protocol {configuration.protocol_id!r}") + runtime = {**preimport_runtime, **measurement.runtime} + return _write_measurement_bundle( + spec, + support_set, + measurement, + _BundleContext(runtime, environment.to_dict(), platform_descriptor.to_dict()), + artifact_root, + ) + + +def _load_provenance(spec: RunSpec) -> tuple[EnvironmentDescriptor, PlatformDescriptor]: + environment = load_environment_descriptor(spec.environment_descriptor) + platform_descriptor = load_platform_descriptor(spec.platform_descriptor) + if environment.environment_id != spec.identity.environment_id: + raise MeasurementError("environment descriptor does not match run identity") + if environment.runner_revision != spec.identity.runner_revision: + raise MeasurementError("environment descriptor runner revision does not match run identity") + if platform_descriptor.platform_id != spec.identity.platform_id: + raise MeasurementError("platform descriptor does not match run identity") + return environment, platform_descriptor + + +def _load_decoder(configuration: RunConfiguration) -> tuple[BaseDecoder, int]: + from imread_benchmark.decoders import REGISTRY + + decoder_class = REGISTRY.get(configuration.decoder_id) + if decoder_class is None: + raise MeasurementError(f"unknown decoder {configuration.decoder_id!r}") + if not decoder_class.runs_single_here(): + raise MeasurementError(f"decoder {configuration.decoder_id!r} does not support this platform") + decoder = decoder_class() + return decoder, configure_decoder(decoder, configuration.requested_threads) + + +def _load_selected_items(spec: RunSpec) -> tuple[ResidentItem, ...]: + configuration = spec.identity.configuration + package = open_dataset_package(spec.package_descriptor) + if package.descriptor.get("package_id") != configuration.package_id: + raise MeasurementError("run spec package_id does not match dataset descriptor") + workload = package.descriptor.get("workloads", {}).get(spec.identity.workload_id) + if not isinstance(workload, dict) or workload.get("manifest_id") != configuration.manifest_id: + raise MeasurementError("run spec workload manifest_id does not match dataset descriptor") + all_items = package.read_workload_items(spec.identity.workload_id) + by_id = {item.item_id: item for item in all_items} + if len(by_id) != len(all_items): + raise MeasurementError("dataset workload contains duplicate item IDs") + missing = [item_id for item_id in spec.identity.support_item_ids if item_id not in by_id] + if missing: + raise MeasurementError(f"support set refers to missing dataset items: {missing[:3]}") + return tuple(by_id[item_id] for item_id in spec.identity.support_item_ids) + + +def _load_pinned_support_set(spec: RunSpec) -> SupportSet: + configuration = spec.identity.configuration + try: + support_set = load_support_set(spec.support_set_path) + except (TypeError, ValueError) as exc: + raise MeasurementError(f"invalid pinned support set: {exc}") from exc + expected_process_context = ( + "dataloader" if configuration.protocol_id == "loader-supply" and configuration.num_workers else "main-process" + ) + if ( + support_set.support_set_id != spec.identity.support_set_id + or support_set.manifest_id != configuration.manifest_id + or support_set.selection_id != configuration.selection_id + or support_set.policy != configuration.support_policy + or support_set.process_context != expected_process_context + or support_set.multiprocessing_start_method != configuration.multiprocessing_start_method + or support_set.item_ids != spec.identity.support_item_ids + ): + raise MeasurementError("pinned support set does not match run specification") + return support_set + + +def _write_measurement_bundle( + spec: RunSpec, + support_set: SupportSet, + measurement: MeasurementResult, + context: _BundleContext, + artifact_root: str | Path, +) -> Path: + configuration = spec.identity.configuration + return write_run_bundle( + root=Path(artifact_root) / "runs", + run_key=spec.run_key, + data=BundleData( + config={ + **asdict(configuration), + "block_position": spec.identity.block_position, + "config_id": configuration.config_id, + "plan_id": spec.identity.plan_id, + "repetition": spec.identity.repetition, + "run_key": spec.run_key, + "runner_revision": spec.identity.runner_revision, + }, + dataset={ + "manifest_id": configuration.manifest_id, + "ordered_item_ids": list(spec.identity.support_item_ids), + "package_id": configuration.package_id, + "selection_id": configuration.selection_id, + "support_set_id": spec.identity.support_set_id, + "support_audit_ids": list(support_set.audit_ids), + "support_policy": support_set.policy, + "support_process_context": support_set.process_context, + "support_multiprocessing_start_method": support_set.multiprocessing_start_method, + "workload_id": spec.identity.workload_id, + }, + environment=context.environment, + platform=context.platform, + runtime=context.runtime, + samples=measurement.samples, + summary_fields=measurement.summary_fields, + events=measurement.events, + ), + ) + + +def main() -> int: + parser = argparse.ArgumentParser() + parser.add_argument("--run-spec", required=True, type=Path) + parser.add_argument("--artifact-root", required=True, type=Path) + args = parser.parse_args() + try: + run_worker(args.run_spec, args.artifact_root) + except RunSpecError as exc: + print(f"run-spec error: {exc}", file=sys.stderr) + return 20 + except DatasetPackageError as exc: + print(f"dataset error: {exc}", file=sys.stderr) + return 21 + except MeasurementError as exc: + print(f"measurement contract error: {exc}", file=sys.stderr) + return 22 + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/imread_benchmark/plans/__init__.py b/imread_benchmark/plans/__init__.py new file mode 100644 index 0000000..30df387 --- /dev/null +++ b/imread_benchmark/plans/__init__.py @@ -0,0 +1,14 @@ +from imread_benchmark.plans.expand import RunConfiguration, RunTemplate, expand_experiment_plan +from imread_benchmark.plans.instantiate import InstantiatedPlan, instantiate_experiment_plans +from imread_benchmark.plans.model import ExperimentPlan, PlanError, load_experiment_plan + +__all__ = [ + "ExperimentPlan", + "InstantiatedPlan", + "PlanError", + "RunConfiguration", + "RunTemplate", + "expand_experiment_plan", + "instantiate_experiment_plans", + "load_experiment_plan", +] diff --git a/imread_benchmark/plans/expand.py b/imread_benchmark/plans/expand.py new file mode 100644 index 0000000..3ebf393 --- /dev/null +++ b/imread_benchmark/plans/expand.py @@ -0,0 +1,403 @@ +from __future__ import annotations + +import hashlib +import json +import random +import re +from dataclasses import asdict, dataclass +from typing import Any + +from imread_benchmark.plans.model import ExperimentPlan, PlanError + +_DIGEST = re.compile(r"^[0-9a-f]{64}$") + + +@dataclass(frozen=True, slots=True) +class RunConfiguration: + protocol_id: str + decoder_id: str + package_id: str + manifest_id: str + selection_id: str + requested_threads: int | None + num_workers: int | None + batch_size: int | None + prefetch_factor: int | None + persistent_workers: bool + multiprocessing_start_method: str | None + logical_repeat_factor: int + warmup_passes: int + timed_passes_per_run: int + minimum_timed_seconds: float + output_contract: str + support_policy: str + + def __post_init__(self) -> None: + self._validate_core_fields() + if self.protocol_id == "decode-memory": + self._validate_decode_fields() + else: + self._validate_loader_fields() + + def _validate_core_fields(self) -> None: + if self.protocol_id not in {"decode-memory", "loader-supply"}: + raise ValueError(f"unsupported protocol_id: {self.protocol_id!r}") + if not self.decoder_id: + raise ValueError("decoder_id must not be empty") + for field_name in ("package_id", "manifest_id", "selection_id"): + if _DIGEST.fullmatch(getattr(self, field_name)) is None: + raise ValueError(f"{field_name} must be a lowercase SHA-256 digest") + if self.requested_threads is not None and ( + isinstance(self.requested_threads, bool) + or not isinstance(self.requested_threads, int) + or self.requested_threads <= 0 + ): + raise ValueError("requested_threads must be a positive integer or default") + if self.logical_repeat_factor <= 0 or self.timed_passes_per_run <= 0: + raise ValueError("logical_repeat_factor and timed_passes_per_run must be positive") + if self.warmup_passes < 0 or self.minimum_timed_seconds <= 0: + raise ValueError("warmup_passes must be non-negative and minimum_timed_seconds positive") + if self.output_contract != "normalized-rgb": + raise ValueError("output_contract must be normalized-rgb") + if self.support_policy not in {"common", "operational"}: + raise ValueError("support_policy must be common or operational") + + def _validate_decode_fields(self) -> None: + if ( + self.num_workers is not None + or self.batch_size is not None + or self.prefetch_factor is not None + or self.persistent_workers + or self.multiprocessing_start_method is not None + ): + raise ValueError("decode-memory does not accept DataLoader fields") + + def _validate_loader_fields(self) -> None: + if self.num_workers is None or self.num_workers < 0 or self.batch_size is None or self.batch_size <= 0: + raise ValueError("loader-supply requires non-negative workers and positive batch_size") + if self.num_workers == 0: + if ( + self.prefetch_factor is not None + or self.persistent_workers + or self.multiprocessing_start_method is not None + ): + raise ValueError( + "loader-supply workers=0 requires inapplicable prefetch, persistence, and " + "multiprocessing_start_method", + ) + elif self.prefetch_factor is None or self.prefetch_factor <= 0 or not self.persistent_workers: + raise ValueError("loader-supply worker processes require prefetch and persistent_workers") + if self.num_workers > 0 and self.multiprocessing_start_method not in {"fork", "forkserver", "spawn"}: + raise ValueError( + "loader-supply worker processes require multiprocessing_start_method=fork, forkserver, or spawn", + ) + if self.num_workers > 0 and self.warmup_passes == 0: + raise ValueError("loader-supply worker processes require at least one warmup pass") + + @property + def config_id(self) -> str: + return _digest(asdict(self)) + + +@dataclass(frozen=True, slots=True) +class RunTemplate: + plan_id: str + template_id: str + configuration: RunConfiguration + repetition: int + position: int + + +@dataclass(frozen=True, slots=True) +class _ConfigurationSeed: + plan: ExperimentPlan + measurement: dict[str, Any] + decoder_id: str + threads: int | None + + +@dataclass(frozen=True, slots=True) +class _WorkerSettings: + num_workers: int + batch_size: int + prefetch_factor: int | None + persistent_workers: bool + multiprocessing_start_method: str | None + + +def expand_experiment_plan(plan: ExperimentPlan) -> tuple[RunTemplate, ...]: + configurations = _configurations(plan) + by_id = {configuration.config_id: configuration for configuration in configurations} + if len(by_id) != len(configurations): + raise PlanError("experiment plan expands to duplicate configurations") + plan_id = _plan_id(plan) + generator = random.Random(plan.seed) # noqa: S311 - scientific randomization is intentionally reproducible + templates: list[RunTemplate] = [] + for repetition in range(plan.repetitions): + block = list(by_id) + generator.shuffle(block) + for config_id in block: + identity = { + "configuration_id": config_id, + "plan_id": plan_id, + "repetition": repetition, + } + templates.append( + RunTemplate( + plan_id=plan_id, + template_id=_digest(identity), + configuration=by_id[config_id], + repetition=repetition, + position=len(templates), + ), + ) + return tuple(templates) + + +def _configurations(plan: ExperimentPlan) -> tuple[RunConfiguration, ...]: + if set(plan.matrix) != {"decoders", "protocols"}: + raise PlanError("matrix fields must be exactly decoders and protocols") + decoders = _decoder_profiles(plan.matrix) + protocols = _object(plan.matrix, "protocols") + measurement = _measurement(plan.measurement) + configurations: list[RunConfiguration] = [] + decode_memory = protocols.get("decode-memory") + if decode_memory is not None: + configurations.extend(_decode_configurations(plan, measurement, decoders, decode_memory)) + loader_supply = protocols.get("loader-supply") + if loader_supply is not None: + configurations.extend(_loader_protocol_configurations(plan, measurement, decoders, loader_supply)) + if not configurations: + raise PlanError("matrix.protocols must select at least one protocol") + return tuple(configurations) + + +def _decode_configurations( + plan: ExperimentPlan, + measurement: dict[str, Any], + decoders: tuple[tuple[str, tuple[int | None, ...]], ...], + protocol: object, +) -> list[RunConfiguration]: + if not isinstance(protocol, dict): + raise PlanError("matrix.protocols.decode-memory must be an object") + if protocol: + raise PlanError("matrix.protocols.decode-memory does not accept fields") + return [ + _configuration( + _ConfigurationSeed(plan, measurement, decoder_id, threads), + protocol_id="decode-memory", + ) + for decoder_id, thread_profile in decoders + for threads in thread_profile + ] + + +def _loader_protocol_configurations( + plan: ExperimentPlan, + measurement: dict[str, Any], + decoders: tuple[tuple[str, tuple[int | None, ...]], ...], + protocol: object, +) -> list[RunConfiguration]: + if not isinstance(protocol, dict): + raise PlanError("matrix.protocols.loader-supply must be an object") + if set(protocol) != {"worker_profiles"}: + raise PlanError("matrix.protocols.loader-supply accepts only worker_profiles") + configurations: list[RunConfiguration] = [] + for decoder_id, thread_profile in decoders: + for threads in thread_profile: + configurations.extend( + _loader_configurations( + _ConfigurationSeed(plan, measurement, decoder_id, threads), + protocol, + ), + ) + return configurations + + +def _decoder_profiles(matrix: dict[str, Any]) -> tuple[tuple[str, tuple[int | None, ...]], ...]: + from imread_benchmark.decoders import REGISTRY, BaseDecoder + + document = matrix.get("decoders") + if not isinstance(document, dict) or not document: + raise PlanError("matrix.decoders must be a non-empty object") + profiles: list[tuple[str, tuple[int | None, ...]]] = [] + for decoder_id, raw_profile in sorted(document.items()): + if not isinstance(decoder_id, str) or not decoder_id or not isinstance(raw_profile, dict): + raise PlanError("each decoder profile must be a named object") + if set(raw_profile) != {"threads"}: + raise PlanError(f"decoder {decoder_id!r} profile accepts only threads") + decoder = REGISTRY.get(decoder_id) + if decoder is None: + raise PlanError(f"unknown decoder in experiment plan: {decoder_id}") + threads = _thread_list(raw_profile, "threads") + if any(value is not None for value in threads) and decoder.set_num_threads is BaseDecoder.set_num_threads: + raise PlanError(f"decoder {decoder_id!r} does not expose thread control") + profiles.append((decoder_id, threads)) + return tuple(profiles) + + +def _configuration( + seed: _ConfigurationSeed, + *, + protocol_id: str, + workers: _WorkerSettings | None = None, +) -> RunConfiguration: + return RunConfiguration( + protocol_id=protocol_id, + decoder_id=seed.decoder_id, + package_id=seed.plan.dataset.package_id, + manifest_id=seed.plan.dataset.manifest_id, + selection_id=seed.plan.dataset.selection.selection_id, + requested_threads=seed.threads, + num_workers=workers.num_workers if workers is not None else None, + batch_size=workers.batch_size if workers is not None else None, + prefetch_factor=workers.prefetch_factor if workers is not None else None, + persistent_workers=workers.persistent_workers if workers is not None else False, + multiprocessing_start_method=workers.multiprocessing_start_method if workers is not None else None, + logical_repeat_factor=seed.plan.dataset.logical_repeat_factor, + warmup_passes=seed.measurement["warmup_passes"], + timed_passes_per_run=seed.measurement["timed_passes_per_run"], + minimum_timed_seconds=seed.measurement["minimum_timed_seconds"], + output_contract=seed.measurement["output_contract"], + support_policy=seed.measurement["support_policy"], + ) + + +def _loader_configurations( + seed: _ConfigurationSeed, + protocol: dict[str, Any], +) -> list[RunConfiguration]: + profiles = protocol.get("worker_profiles") + if not isinstance(profiles, list) or not profiles: + raise PlanError("loader-supply.worker_profiles must be a non-empty list") + configurations: list[RunConfiguration] = [] + for profile in profiles: + if not isinstance(profile, dict): + raise PlanError("loader-supply worker profile must be an object") + workers_values = _non_negative_int_list(profile, "workers") + has_zero = 0 in workers_values + has_processes = any(value > 0 for value in workers_values) + if has_zero and has_processes: + raise PlanError("worker profile must not mix workers=0 with worker processes") + batch_size = _positive_int(profile, "batch_size") + prefetch = None if has_zero else _positive_int(profile, "prefetch_factor") + persistent = False if has_zero else profile.get("persistent_workers") is True + if has_processes and not persistent: + raise PlanError("loader-supply worker processes require persistent_workers=true") + start_method = None if has_zero else _multiprocessing_start_method(profile) + configurations.extend( + _configuration( + seed, + protocol_id="loader-supply", + workers=_WorkerSettings( + num_workers=workers, + batch_size=batch_size, + prefetch_factor=prefetch, + persistent_workers=persistent, + multiprocessing_start_method=start_method, + ), + ) + for workers in workers_values + ) + return configurations + + +def _multiprocessing_start_method(profile: dict[str, Any]) -> str: + value = profile.get("multiprocessing_start_method") + if value not in {"fork", "forkserver", "spawn"}: + raise PlanError( + "loader-supply worker process profile requires multiprocessing_start_method=fork, forkserver, or spawn", + ) + return value + + +def _measurement(document: dict[str, Any]) -> dict[str, Any]: + minimum = document.get("minimum_timed_seconds") + if isinstance(minimum, bool) or not isinstance(minimum, (int, float)) or minimum <= 0: + raise PlanError("measurement.minimum_timed_seconds must be positive") + output_contract = document.get("output_contract") + if output_contract != "normalized-rgb": + raise PlanError("measurement.output_contract must be normalized-rgb") + support_policy = document.get("support_policy") + if support_policy not in {"common", "operational"}: + raise PlanError("measurement.support_policy must be common or operational") + warmup = document.get("warmup_passes") + if isinstance(warmup, bool) or not isinstance(warmup, int) or warmup < 0: + raise PlanError("measurement.warmup_passes must be a non-negative integer") + return { + "minimum_timed_seconds": float(minimum), + "output_contract": output_contract, + "support_policy": support_policy, + "timed_passes_per_run": _positive_int(document, "timed_passes_per_run"), + "warmup_passes": warmup, + } + + +def _plan_id(plan: ExperimentPlan) -> str: + return _digest( + { + "dataset": { + "logical_repeat_factor": plan.dataset.logical_repeat_factor, + "manifest_id": plan.dataset.manifest_id, + "package_id": plan.dataset.package_id, + "selection_id": plan.dataset.selection.selection_id, + "workload_id": plan.dataset.workload_id, + }, + "execution": plan.execution, + "experiment_name": plan.experiment_name, + "matrix": plan.matrix, + "measurement": plan.measurement, + "repetitions": plan.repetitions, + "schema_version": plan.schema_version, + "seed": plan.seed, + }, + ) + + +def _thread_list(payload: dict[str, Any], key: str) -> tuple[int | None, ...]: + values = payload.get(key) + if not isinstance(values, list) or not values: + raise PlanError(f"field {key!r} must be a non-empty list") + result: list[int | None] = [] + for value in values: + if value == "default": + result.append(None) + elif not isinstance(value, bool) and isinstance(value, int) and value > 0: + result.append(value) + else: + raise PlanError(f"field {key!r} accepts positive integers or 'default'") + if len(set(result)) != len(result): + raise PlanError(f"field {key!r} must contain unique values") + return tuple(result) + + +def _non_negative_int_list(payload: dict[str, Any], key: str) -> tuple[int, ...]: + values = payload.get(key) + if ( + not isinstance(values, list) + or not values + or any(isinstance(value, bool) or not isinstance(value, int) or value < 0 for value in values) + ): + raise PlanError(f"field {key!r} must be a non-empty list of non-negative integers") + if len(set(values)) != len(values): + raise PlanError(f"field {key!r} must contain unique values") + return tuple(values) + + +def _positive_int(payload: dict[str, Any], key: str) -> int: + value = payload.get(key) + if isinstance(value, bool) or not isinstance(value, int) or value <= 0: + raise PlanError(f"field {key!r} must be a positive integer") + return value + + +def _object(payload: dict[str, Any], key: str) -> dict[str, Any]: + value = payload.get(key) + if not isinstance(value, dict): + raise PlanError(f"field {key!r} must be an object") + return value + + +def _digest(payload: object) -> str: + canonical = json.dumps(payload, ensure_ascii=False, separators=(",", ":"), sort_keys=True) + return hashlib.sha256(canonical.encode()).hexdigest() diff --git a/imread_benchmark/plans/instantiate.py b/imread_benchmark/plans/instantiate.py new file mode 100644 index 0000000..e7266de --- /dev/null +++ b/imread_benchmark/plans/instantiate.py @@ -0,0 +1,170 @@ +from __future__ import annotations + +import os +import tempfile +from dataclasses import dataclass +from pathlib import Path +from typing import Any + +import yaml + +from imread_benchmark.datasets.package import DatasetPackageError, open_dataset_package +from imread_benchmark.plans.expand import expand_experiment_plan +from imread_benchmark.plans.model import PlanError, load_experiment_plan + +_PLACEHOLDERS = ("PACKAGE_ID", "WORKLOAD_ID", "MANIFEST_ID", "ITEM_COUNT") + + +@dataclass(frozen=True, slots=True) +class InstantiatedPlan: + workload_id: str + manifest_id: str + item_count: int + plan_id: str + run_count: int + path: Path + + def to_dict(self) -> dict[str, object]: + return { + "item_count": self.item_count, + "manifest_id": self.manifest_id, + "path": str(self.path), + "plan_id": self.plan_id, + "run_count": self.run_count, + "workload_id": self.workload_id, + } + + +def instantiate_experiment_plans( + *, + template_path: str | Path, + package_descriptor: str | Path, + output_dir: str | Path, + workload_ids: tuple[str, ...] = (), +) -> tuple[InstantiatedPlan, ...]: + """Fill dataset identities in a schema-2 template and validate every output plan.""" + template = _read_template(Path(template_path).resolve()) + descriptor_path = Path(package_descriptor).resolve() + try: + package = open_dataset_package(descriptor_path) + except DatasetPackageError as exc: + raise PlanError(f"invalid dataset package: {exc}") from exc + package_id = _required_string(package.descriptor, "package_id") + workloads = package.descriptor.get("workloads") + if not isinstance(workloads, dict) or not workloads: + raise PlanError("dataset package has no workloads") + selected = _select_workloads(workloads, workload_ids) + + destination = Path(output_dir).resolve() + destination.mkdir(parents=True, exist_ok=True) + results: list[InstantiatedPlan] = [] + for workload_id in selected: + raw_workload = workloads[workload_id] + if not isinstance(raw_workload, dict): + raise PlanError(f"dataset package workload {workload_id!r} must be an object") + manifest_id = _required_string(raw_workload, "manifest_id") + item_count = _required_positive_int(raw_workload, "item_count") + document = _replace_placeholders( + template, + { + "ITEM_COUNT": item_count, + "MANIFEST_ID": manifest_id, + "PACKAGE_ID": package_id, + "WORKLOAD_ID": workload_id, + }, + ) + if _find_placeholders(document): + raise PlanError(f"unresolved plan template placeholders: {sorted(_find_placeholders(document))}") + output_path = destination / f"{workload_id}.yaml" + temporary_path = _write_temporary_yaml(output_path, document) + try: + plan = load_experiment_plan(temporary_path, dataset_descriptor=descriptor_path) + templates = expand_experiment_plan(plan) + temporary_path.replace(output_path) + finally: + temporary_path.unlink(missing_ok=True) + results.append( + InstantiatedPlan( + workload_id=workload_id, + manifest_id=manifest_id, + item_count=item_count, + plan_id=templates[0].plan_id, + run_count=len(templates), + path=output_path, + ), + ) + return tuple(results) + + +def _read_template(path: Path) -> dict[str, Any]: + try: + document = yaml.safe_load(path.read_text()) + except (OSError, yaml.YAMLError) as exc: + raise PlanError(f"cannot read experiment plan template {path}: {exc}") from exc + if not isinstance(document, dict): + raise PlanError(f"experiment plan template must be a YAML object: {path}") + if document.get("schema_version") != "2.0": + raise PlanError(f"unsupported experiment plan template schema: {document.get('schema_version')!r}") + return document + + +def _select_workloads(workloads: dict[str, object], requested: tuple[str, ...]) -> tuple[str, ...]: + if len(requested) != len(set(requested)): + raise PlanError("requested workload IDs must be unique") + selected = requested or tuple(sorted(workloads)) + unknown = tuple(workload_id for workload_id in selected if workload_id not in workloads) + if unknown: + raise PlanError(f"dataset package has no requested workloads: {list(unknown)}") + return selected + + +def _replace_placeholders(value: object, replacements: dict[str, str | int]) -> object: + if isinstance(value, dict): + return {key: _replace_placeholders(item, replacements) for key, item in value.items()} + if isinstance(value, list): + return [_replace_placeholders(item, replacements) for item in value] + if not isinstance(value, str): + return value + if value in replacements: + return replacements[value] + result = value + for placeholder, replacement in replacements.items(): + result = result.replace(placeholder, str(replacement)) + return result + + +def _find_placeholders(value: object) -> set[str]: + if isinstance(value, dict): + return set().union(*(_find_placeholders(item) for item in value.values()), set()) + if isinstance(value, list): + return set().union(*(_find_placeholders(item) for item in value), set()) + if isinstance(value, str): + return {placeholder for placeholder in _PLACEHOLDERS if placeholder in value} + return set() + + +def _write_temporary_yaml(path: Path, payload: object) -> Path: + descriptor, name = tempfile.mkstemp(dir=path.parent, prefix=f".{path.name}.") + temporary = Path(name) + try: + with os.fdopen(descriptor, mode="w") as file: + yaml.safe_dump(payload, file, allow_unicode=True, sort_keys=False) + except Exception: + temporary.unlink(missing_ok=True) + raise + else: + return temporary + + +def _required_string(payload: dict[str, Any], key: str) -> str: + value = payload.get(key) + if not isinstance(value, str) or not value: + raise PlanError(f"field {key!r} must be a non-empty string") + return value + + +def _required_positive_int(payload: dict[str, Any], key: str) -> int: + value = payload.get(key) + if isinstance(value, bool) or not isinstance(value, int) or value <= 0: + raise PlanError(f"field {key!r} must be a positive integer") + return value diff --git a/imread_benchmark/plans/model.py b/imread_benchmark/plans/model.py new file mode 100644 index 0000000..b635e1c --- /dev/null +++ b/imread_benchmark/plans/model.py @@ -0,0 +1,219 @@ +from __future__ import annotations + +import hashlib +import json +from dataclasses import dataclass +from pathlib import Path +from typing import Any, cast + +import yaml + +from imread_benchmark.datasets.package import DatasetPackageError, open_dataset_package + +PLAN_SCHEMA_VERSION = "2.0" + + +class PlanError(ValueError): + pass + + +@dataclass(frozen=True, slots=True) +class DatasetSelection: + method: str + item_ids: tuple[str, ...] + selection_id: str + + +@dataclass(frozen=True, slots=True) +class PlannedDataset: + descriptor_path: Path + package_id: str + workload_id: str + manifest_id: str + manifest: dict[str, Any] + selection: DatasetSelection + logical_repeat_factor: int + + +@dataclass(frozen=True, slots=True) +class ExperimentPlan: + schema_version: str + experiment_name: str + seed: int + repetitions: int + dataset: PlannedDataset + matrix: dict[str, Any] + measurement: dict[str, Any] + execution: dict[str, Any] + + +def load_experiment_plan( + path: str | Path, + *, + dataset_descriptor: str | Path | None = None, +) -> ExperimentPlan: + plan_path = Path(path).resolve() + document = _read_yaml_object(plan_path) + if document.get("schema_version") != PLAN_SCHEMA_VERSION: + raise PlanError(f"unsupported experiment plan schema: {document.get('schema_version')!r}") + + dataset_document = _required_object(document, "dataset") + descriptor_path = ( + Path(dataset_descriptor) + if dataset_descriptor is not None + else Path(_required_string(dataset_document, "descriptor")) + ) + if descriptor_path.is_absolute(): + descriptor_path = descriptor_path.resolve() + else: + descriptor_path = (plan_path.parent / descriptor_path).resolve() + try: + package = open_dataset_package(descriptor_path) + except DatasetPackageError as exc: + raise PlanError(f"invalid dataset package: {exc}") from exc + + package_id = _required_string(dataset_document, "package_id") + if package.descriptor.get("package_id") != package_id: + raise PlanError("dataset package_id does not match descriptor") + workload_id = _required_string(dataset_document, "workload_id") + workloads = _required_object(package.descriptor, "workloads") + workload = workloads.get(workload_id) + if not isinstance(workload, dict): + raise PlanError(f"dataset package has no workload {workload_id!r}") + manifest_id = _required_string(dataset_document, "manifest_id") + if workload.get("manifest_id") != manifest_id: + raise PlanError("dataset manifest_id does not match package workload") + + manifest_path = package.root / _required_string(workload, "manifest") + manifest = _read_json_object(manifest_path) + selection = _load_selection(_required_object(dataset_document, "selection"), manifest, manifest_id) + logical_repeat_factor = _required_positive_int(dataset_document, "logical_repeat_factor") + execution = _load_execution(_required_object(document, "execution")) + + return ExperimentPlan( + schema_version=PLAN_SCHEMA_VERSION, + experiment_name=_required_string(document, "experiment_name"), + seed=_required_int(document, "seed"), + repetitions=_required_positive_int(document, "repetitions"), + dataset=PlannedDataset( + descriptor_path=descriptor_path, + package_id=package_id, + workload_id=workload_id, + manifest_id=manifest_id, + manifest=manifest, + selection=selection, + logical_repeat_factor=logical_repeat_factor, + ), + matrix=_required_object(document, "matrix"), + measurement=_required_object(document, "measurement"), + execution=execution, + ) + + +def _load_execution(document: dict[str, Any]) -> dict[str, object]: + expected = { + "checkpoint_each_run", + "maximum_memory_fraction", + "per_run_subprocess", + "run_timeout_seconds", + } + if set(document) != expected: + raise PlanError(f"execution fields must be exactly {sorted(expected)}") + if document.get("per_run_subprocess") is not True: + raise PlanError("execution.per_run_subprocess must be true") + if document.get("checkpoint_each_run") is not True: + raise PlanError("execution.checkpoint_each_run must be true") + timeout = _required_positive_number(document, "run_timeout_seconds") + fraction = _required_positive_number(document, "maximum_memory_fraction") + if fraction > 0.9: + raise PlanError("execution.maximum_memory_fraction must be at most 0.9") + return { + "checkpoint_each_run": True, + "maximum_memory_fraction": fraction, + "per_run_subprocess": True, + "run_timeout_seconds": timeout, + } + + +def _load_selection( + document: dict[str, Any], + manifest: dict[str, Any], + manifest_id: str, +) -> DatasetSelection: + method = _required_string(document, "method") + if method != "all": + raise PlanError(f"unsupported dataset selection method: {method!r}") + items = manifest.get("items") + if not isinstance(items, list) or not all(isinstance(item, dict) for item in items): + raise PlanError("dataset manifest has no valid items list") + expected_items = _required_positive_int(document, "expected_items") + if len(items) != expected_items: + raise PlanError(f"dataset selection expected {expected_items} items, found {len(items)}") + item_ids = tuple(_required_string(cast("dict[str, Any]", item), "item_id") for item in items) + identity = { + "item_ids": item_ids, + "manifest_id": manifest_id, + "method": method, + "schema_version": PLAN_SCHEMA_VERSION, + } + return DatasetSelection(method=method, item_ids=item_ids, selection_id=_digest_json(identity)) + + +def _read_yaml_object(path: Path) -> dict[str, Any]: + try: + value = yaml.safe_load(path.read_text()) + except (OSError, yaml.YAMLError) as exc: + raise PlanError(f"cannot read experiment plan {path}: {exc}") from exc + if not isinstance(value, dict): + raise PlanError(f"experiment plan must be a YAML object: {path}") + return value + + +def _read_json_object(path: Path) -> dict[str, Any]: + try: + value = json.loads(path.read_text()) + except (OSError, json.JSONDecodeError) as exc: + raise PlanError(f"cannot read dataset manifest {path}: {exc}") from exc + if not isinstance(value, dict): + raise PlanError(f"dataset manifest must be a JSON object: {path}") + return value + + +def _required_object(payload: dict[str, Any], key: str) -> dict[str, Any]: + value = payload.get(key) + if not isinstance(value, dict): + raise PlanError(f"field {key!r} must be an object") + return value + + +def _required_string(payload: dict[str, Any], key: str) -> str: + value = payload.get(key) + if not isinstance(value, str) or not value: + raise PlanError(f"field {key!r} must be a non-empty string") + return value + + +def _required_int(payload: dict[str, Any], key: str) -> int: + value = payload.get(key) + if isinstance(value, bool) or not isinstance(value, int): + raise PlanError(f"field {key!r} must be an integer") + return value + + +def _required_positive_int(payload: dict[str, Any], key: str) -> int: + value = _required_int(payload, key) + if value <= 0: + raise PlanError(f"field {key!r} must be positive") + return value + + +def _required_positive_number(payload: dict[str, Any], key: str) -> float: + value = payload.get(key) + if isinstance(value, bool) or not isinstance(value, (int, float)) or value <= 0: + raise PlanError(f"field {key!r} must be a positive number") + return float(value) + + +def _digest_json(payload: object) -> str: + canonical = json.dumps(payload, ensure_ascii=False, separators=(",", ":"), sort_keys=True) + return hashlib.sha256(canonical.encode()).hexdigest() diff --git a/imread_benchmark/platforms.py b/imread_benchmark/platforms.py new file mode 100644 index 0000000..af9e645 --- /dev/null +++ b/imread_benchmark/platforms.py @@ -0,0 +1,231 @@ +from __future__ import annotations + +import hashlib +import json +import os +import platform +import tempfile +from dataclasses import dataclass +from pathlib import Path + +PLATFORM_SCHEMA_VERSION = "2.0" +_THREAD_ENVIRONMENT_VARIABLES = ( + "BLIS_NUM_THREADS", + "MKL_NUM_THREADS", + "OMP_NUM_THREADS", + "OPENBLAS_NUM_THREADS", + "VECLIB_MAXIMUM_THREADS", +) + + +@dataclass(frozen=True, slots=True) +class PlatformDescriptor: + platform_id: str + identity: dict[str, object] + runtime: dict[str, object] + provenance: dict[str, object] + + @classmethod + def build( + cls, + *, + identity: dict[str, object], + runtime: dict[str, object], + provenance: dict[str, object] | None = None, + ) -> PlatformDescriptor: + if not identity or any(not isinstance(key, str) or not key for key in identity): + raise ValueError("platform identity must be a non-empty object") + if any(not isinstance(key, str) or not key for key in runtime): + raise ValueError("platform runtime keys must be non-empty strings") + if provenance is not None and any(not isinstance(key, str) or not key for key in provenance): + raise ValueError("platform provenance keys must be non-empty strings") + normalized_identity = _canonical_object(identity) + normalized_runtime = _canonical_object(runtime) + normalized_provenance = _canonical_object(provenance or {}) + platform_id = _digest( + { + "identity": normalized_identity, + "schema_version": PLATFORM_SCHEMA_VERSION, + }, + ) + return cls( + platform_id=platform_id, + identity=normalized_identity, + runtime=normalized_runtime, + provenance=normalized_provenance, + ) + + def to_dict(self) -> dict[str, object]: + return { + "identity": self.identity, + "platform_id": self.platform_id, + "provenance": self.provenance, + "runtime": self.runtime, + "schema_version": PLATFORM_SCHEMA_VERSION, + } + + @property + def comparison_platform_id(self) -> str: + """Stable comparison key that deliberately excludes execution zone.""" + return _comparison_platform_id(self.identity) + + +def capture_current_platform( + *, + cloud_provider: str, + machine_type: str, + location: str, +) -> PlatformDescriptor: + try: + import cpuinfo + + cpu = cpuinfo.get_cpu_info() + except Exception as exc: + raise ValueError(f"cannot probe CPU information: {exc}") from exc + logical_cpu_count = os.cpu_count() + if logical_cpu_count is None or logical_cpu_count <= 0: + raise ValueError("cannot determine logical CPU count") + identity: dict[str, object] = { + "architecture": platform.machine(), + "cloud_provider": cloud_provider, + "cpu_architecture": cpu.get("arch_string_raw") or cpu.get("arch") or platform.machine(), + "cpu_brand": cpu.get("brand_raw") or "unknown", + "cpu_family": cpu.get("family"), + "cpu_model": cpu.get("model"), + "cpu_stepping": cpu.get("stepping"), + "cpu_vendor": cpu.get("vendor_id_raw") or "unknown", + "logical_cpu_count": logical_cpu_count, + "machine_type": machine_type, + "system": platform.system(), + } + runtime: dict[str, object] = { + "available_multiprocessing_start_methods": _multiprocessing_start_methods(), + "kernel_release": platform.release(), + "kernel_version": platform.version(), + "libc": list(platform.libc_ver()), + "memory_bytes": _memory_bytes(), + "thread_environment": {key: os.environ[key] for key in _THREAD_ENVIRONMENT_VARIABLES if key in os.environ}, + } + return PlatformDescriptor.build( + identity=identity, + runtime=runtime, + provenance={"location": location}, + ) + + +def write_platform_descriptor(path: str | Path, descriptor: PlatformDescriptor) -> Path: + path = Path(path) + path.parent.mkdir(parents=True, exist_ok=True) + content = json.dumps(descriptor.to_dict(), ensure_ascii=False, indent=2, sort_keys=True) + "\n" + if path.exists(): + if path.read_text() != content: + raise ValueError(f"immutable platform descriptor already exists with different content: {path}") + return path + temporary: Path | None = None + try: + with tempfile.NamedTemporaryFile(mode="w", dir=path.parent, prefix=f".{path.name}.", delete=False) as file: + file.write(content) + file.flush() + os.fsync(file.fileno()) + temporary = Path(file.name) + try: + os.link(temporary, path) + except FileExistsError: + if path.read_text() != content: + raise ValueError(f"concurrent platform descriptor differs: {path}") from None + finally: + if temporary is not None: + temporary.unlink(missing_ok=True) + return path + + +def load_platform_descriptor(path: str | Path) -> PlatformDescriptor: + source = Path(path) + try: + document = json.loads(source.read_text()) + except (OSError, json.JSONDecodeError) as exc: + raise ValueError(f"cannot read platform descriptor {source}: {exc}") from exc + if not isinstance(document, dict) or document.get("schema_version") != PLATFORM_SCHEMA_VERSION: + raise ValueError("unsupported platform descriptor schema") + identity = document.get("identity") + runtime = document.get("runtime") + provenance = document.get("provenance", {}) + if not isinstance(identity, dict) or not isinstance(runtime, dict) or not isinstance(provenance, dict): + raise TypeError("platform descriptor identity, runtime, and provenance must be objects") + descriptor = PlatformDescriptor.build(identity=identity, runtime=runtime, provenance=provenance) + if document.get("platform_id") != descriptor.platform_id: + raise ValueError("platform_id does not match descriptor content") + return descriptor + + +def platform_comparison_id(document: dict[str, object]) -> str: + """ + Return the zone-independent comparison key for a persisted descriptor. + + Legacy descriptors recorded ``location`` in ``identity``. New descriptors + record it in ``provenance``. Normalizing both forms here keeps already + committed evidence usable without concealing the execution zone. + """ + identity = document.get("identity") + if not isinstance(identity, dict): + raise TypeError("platform descriptor identity must be an object") + return _comparison_platform_id(identity) + + +def platform_location(document: dict[str, object]) -> str | None: + """Return the recorded execution zone from new or legacy provenance.""" + provenance = document.get("provenance") + if isinstance(provenance, dict): + value = provenance.get("location") + if isinstance(value, str) and value: + return value + identity = document.get("identity") + if isinstance(identity, dict): + value = identity.get("location") + if isinstance(value, str) and value: + return value + return None + + +def _canonical_object(value: dict[str, object]) -> dict[str, object]: + try: + canonical = json.dumps(value, ensure_ascii=False, separators=(",", ":"), sort_keys=True, allow_nan=False) + loaded = json.loads(canonical) + except (TypeError, ValueError) as exc: + raise ValueError(f"platform descriptor contains a non-JSON value: {exc}") from exc + if not isinstance(loaded, dict): + raise TypeError("canonical platform object is not a mapping") + return loaded + + +def _digest(payload: object) -> str: + canonical = json.dumps(payload, ensure_ascii=False, separators=(",", ":"), sort_keys=True) + return hashlib.sha256(canonical.encode()).hexdigest() + + +def _comparison_platform_id(identity: dict[str, object]) -> str: + comparison_identity = {key: value for key, value in identity.items() if key != "location"} + normalized_identity = _canonical_object(comparison_identity) + return _digest( + { + "identity": normalized_identity, + "schema_version": PLATFORM_SCHEMA_VERSION, + }, + ) + + +def _multiprocessing_start_methods() -> list[str]: + import multiprocessing + + return sorted(multiprocessing.get_all_start_methods()) + + +def _memory_bytes() -> int | None: + try: + pages = os.sysconf("SC_PHYS_PAGES") + page_size = os.sysconf("SC_PAGE_SIZE") + except (OSError, ValueError): + return None + if not isinstance(pages, int) or not isinstance(page_size, int): + return None + return pages * page_size diff --git a/imread_benchmark/support/__init__.py b/imread_benchmark/support/__init__.py new file mode 100644 index 0000000..ec4fbe4 --- /dev/null +++ b/imread_benchmark/support/__init__.py @@ -0,0 +1,37 @@ +from imread_benchmark.support.audit import ( + SupportAudit, + SupportAuditContext, + SupportFailure, + SupportSet, + audit_decoder_support, + build_common_support, + build_operational_support, + build_support_audit, + intersect_supported_items, +) +from imread_benchmark.support.store import ( + commit_support_audit, + load_committed_support_audit, + load_support_audit, + load_support_set, + write_support_audit, + write_support_set, +) + +__all__ = [ + "SupportAudit", + "SupportAuditContext", + "SupportFailure", + "SupportSet", + "audit_decoder_support", + "build_common_support", + "build_operational_support", + "build_support_audit", + "commit_support_audit", + "intersect_supported_items", + "load_committed_support_audit", + "load_support_audit", + "load_support_set", + "write_support_audit", + "write_support_set", +] diff --git a/imread_benchmark/support/audit.py b/imread_benchmark/support/audit.py new file mode 100644 index 0000000..d20bb6c --- /dev/null +++ b/imread_benchmark/support/audit.py @@ -0,0 +1,292 @@ +from __future__ import annotations + +import hashlib +import json +from dataclasses import asdict, dataclass +from typing import TYPE_CHECKING + +from imread_benchmark.contracts import OutputContract, OutputContractError, validate_output + +if TYPE_CHECKING: + from collections.abc import Sequence + + from imread_benchmark.datasets.package import ResidentItem + from imread_benchmark.decoders import BaseDecoder + +SUPPORT_SCHEMA_VERSION = "2.0" + + +@dataclass(frozen=True, slots=True) +class SupportFailure: + item_id: str + failure_type: str + message: str + + def to_dict(self) -> dict[str, str]: + return asdict(self) + + +@dataclass(frozen=True, slots=True) +class SupportAuditContext: + manifest_id: str + selection_id: str + process_context: str + multiprocessing_start_method: str | None + requested_threads: int | None + environment_id: str + platform_id: str + + +@dataclass(frozen=True, slots=True) +class SupportAudit: + audit_id: str + decoder_id: str + requested_threads: int | None + manifest_id: str + selection_id: str + process_context: str + multiprocessing_start_method: str | None + output_contract: str + environment_id: str + platform_id: str + successful_item_ids: tuple[str, ...] + failures: tuple[SupportFailure, ...] + + def to_dict(self) -> dict[str, object]: + return { + "audit_id": self.audit_id, + "decoder_id": self.decoder_id, + "environment_id": self.environment_id, + "failures": [failure.to_dict() for failure in self.failures], + "manifest_id": self.manifest_id, + "multiprocessing_start_method": self.multiprocessing_start_method, + "output_contract": self.output_contract, + "platform_id": self.platform_id, + "process_context": self.process_context, + "requested_threads": self.requested_threads, + "schema_version": SUPPORT_SCHEMA_VERSION, + "selection_id": self.selection_id, + "successful_item_ids": list(self.successful_item_ids), + } + + +@dataclass(frozen=True, slots=True) +class SupportSet: + support_set_id: str + policy: str + manifest_id: str + selection_id: str + process_context: str + multiprocessing_start_method: str | None + audit_ids: tuple[str, ...] + item_ids: tuple[str, ...] + + def to_dict(self) -> dict[str, object]: + return { + "audit_ids": list(self.audit_ids), + "item_ids": list(self.item_ids), + "manifest_id": self.manifest_id, + "multiprocessing_start_method": self.multiprocessing_start_method, + "policy": self.policy, + "process_context": self.process_context, + "schema_version": SUPPORT_SCHEMA_VERSION, + "selection_id": self.selection_id, + "support_set_id": self.support_set_id, + } + + +@dataclass(frozen=True, slots=True) +class SupportSetIdentity: + policy: str + manifest_id: str + selection_id: str + process_context: str + multiprocessing_start_method: str | None + audit_ids: tuple[str, ...] + item_ids: tuple[str, ...] + + def to_dict(self) -> dict[str, object]: + return { + "audit_ids": list(self.audit_ids), + "item_ids": list(self.item_ids), + "manifest_id": self.manifest_id, + "multiprocessing_start_method": self.multiprocessing_start_method, + "policy": self.policy, + "process_context": self.process_context, + "schema_version": SUPPORT_SCHEMA_VERSION, + "selection_id": self.selection_id, + } + + +def audit_decoder_support( + decoder: BaseDecoder, + items: Sequence[ResidentItem], + context: SupportAuditContext, +) -> SupportAudit: + if not items: + raise ValueError("support audit requires at least one resident item") + successful: list[str] = [] + failures: list[SupportFailure] = [] + contract = OutputContract.normalized_rgb() + for item in items: + try: + validate_output(decoder.decode(item.data), contract) + except Exception as exc: + failures.append( + SupportFailure( + item_id=item.item_id, + failure_type="output_contract_error" if isinstance(exc, OutputContractError) else "decode_error", + message=f"{type(exc).__name__}: {exc}", + ), + ) + else: + successful.append(item.item_id) + return build_support_audit( + decoder_id=decoder.name, + context=context, + successful_item_ids=tuple(successful), + failures=tuple(failures), + ) + + +def build_support_audit( + *, + decoder_id: str, + context: SupportAuditContext, + successful_item_ids: tuple[str, ...], + failures: tuple[SupportFailure, ...], +) -> SupportAudit: + identity = { + "decoder_id": decoder_id, + "environment_id": context.environment_id, + "failures": [asdict(failure) for failure in failures], + "manifest_id": context.manifest_id, + "multiprocessing_start_method": context.multiprocessing_start_method, + "output_contract": "normalized-rgb", + "platform_id": context.platform_id, + "process_context": context.process_context, + "requested_threads": context.requested_threads, + "schema_version": SUPPORT_SCHEMA_VERSION, + "selection_id": context.selection_id, + "successful_item_ids": list(successful_item_ids), + } + return SupportAudit( + audit_id=_digest(identity), + decoder_id=decoder_id, + requested_threads=context.requested_threads, + manifest_id=context.manifest_id, + selection_id=context.selection_id, + process_context=context.process_context, + multiprocessing_start_method=context.multiprocessing_start_method, + output_contract="normalized-rgb", + environment_id=context.environment_id, + platform_id=context.platform_id, + successful_item_ids=successful_item_ids, + failures=failures, + ) + + +def build_common_support( + audits: Sequence[SupportAudit], + *, + ordered_selection: Sequence[str], +) -> SupportSet: + if not audits: + raise ValueError("common support requires at least one audit") + first = audits[0] + for audit in audits[1:]: + if ( + audit.manifest_id != first.manifest_id + or audit.selection_id != first.selection_id + or audit.process_context != first.process_context + or audit.multiprocessing_start_method != first.multiprocessing_start_method + or audit.output_contract != first.output_contract + or audit.platform_id != first.platform_id + or audit.environment_id != first.environment_id + ): + raise ValueError("common support audits describe different workloads or execution contexts") + common_ids = set(first.successful_item_ids) + for audit in audits[1:]: + common_ids.intersection_update(audit.successful_item_ids) + item_ids = tuple(item_id for item_id in ordered_selection if item_id in common_ids) + if not item_ids: + raise ValueError("common support set is empty") + audit_ids = tuple(sorted(audit.audit_id for audit in audits)) + identity = SupportSetIdentity( + policy="common", + manifest_id=first.manifest_id, + selection_id=first.selection_id, + process_context=first.process_context, + multiprocessing_start_method=first.multiprocessing_start_method, + audit_ids=audit_ids, + item_ids=item_ids, + ).to_dict() + return SupportSet( + support_set_id=_digest(identity), + policy="common", + manifest_id=first.manifest_id, + selection_id=first.selection_id, + process_context=first.process_context, + multiprocessing_start_method=first.multiprocessing_start_method, + audit_ids=audit_ids, + item_ids=item_ids, + ) + + +def intersect_supported_items( + audits: Sequence[SupportAudit], + *, + ordered_selection: Sequence[str], +) -> tuple[str, ...]: + """Return one ordered success intersection across all comparison contexts.""" + if not audits: + raise ValueError("support intersection requires at least one audit") + first = audits[0] + for audit in audits[1:]: + if ( + audit.manifest_id != first.manifest_id + or audit.selection_id != first.selection_id + or audit.output_contract != first.output_contract + or audit.platform_id != first.platform_id + or audit.environment_id != first.environment_id + ): + raise ValueError("support audits describe different comparison populations") + successful = set(first.successful_item_ids) + for audit in audits[1:]: + successful.intersection_update(audit.successful_item_ids) + item_ids = tuple(item_id for item_id in ordered_selection if item_id in successful) + if not item_ids: + raise ValueError("comparison support intersection is empty") + if len(item_ids) != len(set(item_ids)): + raise ValueError("ordered selection contains duplicate supported item IDs") + return item_ids + + +def build_operational_support(audit: SupportAudit) -> SupportSet: + if not audit.successful_item_ids: + raise ValueError("operational support set is empty") + audit_ids = (audit.audit_id,) + identity = SupportSetIdentity( + policy="operational", + manifest_id=audit.manifest_id, + selection_id=audit.selection_id, + process_context=audit.process_context, + multiprocessing_start_method=audit.multiprocessing_start_method, + audit_ids=audit_ids, + item_ids=audit.successful_item_ids, + ).to_dict() + return SupportSet( + support_set_id=_digest(identity), + policy="operational", + manifest_id=audit.manifest_id, + selection_id=audit.selection_id, + process_context=audit.process_context, + multiprocessing_start_method=audit.multiprocessing_start_method, + audit_ids=audit_ids, + item_ids=audit.successful_item_ids, + ) + + +def _digest(payload: object) -> str: + canonical = json.dumps(payload, ensure_ascii=False, separators=(",", ":"), sort_keys=True) + return hashlib.sha256(canonical.encode()).hexdigest() diff --git a/imread_benchmark/support/dataloader.py b/imread_benchmark/support/dataloader.py new file mode 100644 index 0000000..bb3475f --- /dev/null +++ b/imread_benchmark/support/dataloader.py @@ -0,0 +1,154 @@ +from __future__ import annotations + +import multiprocessing +import os +from dataclasses import dataclass +from typing import TYPE_CHECKING, Any, cast + +from imread_benchmark.contracts import OutputContract, OutputContractError, validate_output +from imread_benchmark.execution.measurement import configure_decoder +from imread_benchmark.support.audit import ( + SupportAudit, + SupportAuditContext, + SupportFailure, + build_support_audit, +) + +if TYPE_CHECKING: + from collections.abc import Sequence + + from imread_benchmark.datasets.package import ResidentItem + + +@dataclass(frozen=True, slots=True) +class DataLoaderSupportAuditResult: + audit: SupportAudit + worker_handshakes: tuple[dict[str, object], ...] + + +@dataclass(frozen=True, slots=True) +class _WorkerConfig: + decoder_id: str + requested_threads: int | None + multiprocessing_start_method: str + + +class _AuditDataset: + def __init__(self, items: tuple[tuple[str, bytes], ...], worker_config: _WorkerConfig) -> None: + self.items = items + self.worker_config = worker_config + self._decoder: Any = None + self._effective_threads: int | None = None + + def __len__(self) -> int: + return len(self.items) + + def __getitem__(self, index: int) -> dict[str, object]: + item_id, data = self.items[index] + decoder = self._get_decoder() + failure_type: str | None = None + message: str | None = None + try: + validate_output(decoder.decode(data), OutputContract.normalized_rgb()) + except Exception as exc: + failure_type = "output_contract_error" if isinstance(exc, OutputContractError) else "decode_error" + message = f"{type(exc).__name__}: {exc}" + return { + "effective_threads": self._effective_threads, + "failure_type": failure_type, + "item_id": item_id, + "message": message, + "multiprocessing_start_method": self.worker_config.multiprocessing_start_method, + "process_id": os.getpid(), + } + + def _get_decoder(self) -> Any: + if self._decoder is None: + from imread_benchmark.decoders import REGISTRY + + decoder_class = REGISTRY.get(self.worker_config.decoder_id) + if decoder_class is None: + raise RuntimeError(f"unknown decoder {self.worker_config.decoder_id!r}") + self._decoder = decoder_class() + self._effective_threads = configure_decoder(self._decoder, self.worker_config.requested_threads) + return self._decoder + + +def audit_decoder_support_in_dataloader( + decoder_id: str, + items: Sequence[ResidentItem], + context: SupportAuditContext, + *, + requested_threads: int | None, +) -> DataLoaderSupportAuditResult: + if not items: + raise ValueError("support audit requires at least one resident item") + if context.process_context != "dataloader": + raise ValueError("DataLoader support audit requires process_context='dataloader'") + try: + from torch.utils.data import DataLoader + except ImportError as exc: + raise RuntimeError("DataLoader support audit requires PyTorch") from exc + if context.multiprocessing_start_method not in {"fork", "forkserver", "spawn"}: + raise ValueError("DataLoader support audit requires an explicit multiprocessing start method") + try: + multiprocessing_context = multiprocessing.get_context(context.multiprocessing_start_method) + except ValueError as exc: + raise RuntimeError( + f"multiprocessing start method {context.multiprocessing_start_method!r} is unavailable", + ) from exc + dataset = _AuditDataset( + tuple((item.item_id, item.data) for item in items), + _WorkerConfig(decoder_id, requested_threads, multiprocessing_context.get_start_method()), + ) + loader: Any = DataLoader( + cast("Any", dataset), + batch_size=None, + multiprocessing_context=multiprocessing_context, + num_workers=1, + persistent_workers=False, + ) + rows = tuple(loader) + del loader + successful = tuple(_required_string(row, "item_id") for row in rows if row.get("failure_type") is None) + failures = tuple( + SupportFailure( + item_id=_required_string(row, "item_id"), + failure_type=_required_string(row, "failure_type"), + message=_required_string(row, "message"), + ) + for row in rows + if row.get("failure_type") is not None + ) + handshakes_by_pid: dict[int, dict[str, object]] = {} + for row in rows: + process_id = row.get("process_id") + if isinstance(process_id, bool) or not isinstance(process_id, int) or process_id <= 0: + raise RuntimeError("DataLoader support audit returned an invalid worker PID") + handshakes_by_pid.setdefault( + process_id, + { + "effective_threads": row.get("effective_threads"), + "multiprocessing_start_method": row.get("multiprocessing_start_method"), + "process_id": process_id, + }, + ) + if len(handshakes_by_pid) != 1: + raise RuntimeError("DataLoader support audit expected exactly one worker process") + audit = build_support_audit( + decoder_id=decoder_id, + context=context, + successful_item_ids=successful, + failures=failures, + ) + return DataLoaderSupportAuditResult( + audit=audit, + worker_handshakes=tuple(handshakes_by_pid.values()), + ) + + +def _required_string(payload: dict[str, object], key: str) -> str: + value = payload.get(key) + if not isinstance(value, str) or not value: + raise RuntimeError(f"DataLoader support audit field {key!r} must be a non-empty string") + return value diff --git a/imread_benchmark/support/spec.py b/imread_benchmark/support/spec.py new file mode 100644 index 0000000..85e82b4 --- /dev/null +++ b/imread_benchmark/support/spec.py @@ -0,0 +1,184 @@ +from __future__ import annotations + +import hashlib +import json +import re +import tempfile +from dataclasses import asdict, dataclass +from pathlib import Path +from typing import Any + +SUPPORT_AUDIT_SPEC_VERSION = "2.0" +_DIGEST = re.compile(r"^[0-9a-f]{64}$") +_REVISION = re.compile(r"^[0-9a-f]{40}(?:[0-9a-f]{24})?$") + + +class SupportAuditSpecError(ValueError): + pass + + +@dataclass(frozen=True, slots=True) +class SupportAuditIdentity: + decoder_id: str + requested_threads: int | None + package_id: str + workload_id: str + manifest_id: str + selection_id: str + item_ids: tuple[str, ...] + process_context: str + multiprocessing_start_method: str | None + environment_id: str + platform_id: str + runner_revision: str + + def __post_init__(self) -> None: + if not self.decoder_id or not self.workload_id: + raise SupportAuditSpecError("decoder_id and workload_id must not be empty") + for field_name in ("package_id", "manifest_id", "selection_id", "environment_id", "platform_id"): + if _DIGEST.fullmatch(getattr(self, field_name)) is None: + raise SupportAuditSpecError(f"{field_name} must be a lowercase SHA-256 digest") + if self.requested_threads is not None and ( + isinstance(self.requested_threads, bool) + or not isinstance(self.requested_threads, int) + or self.requested_threads <= 0 + ): + raise SupportAuditSpecError("requested_threads must be a positive integer or default") + if ( + not self.item_ids + or len(set(self.item_ids)) != len(self.item_ids) + or any(not item for item in self.item_ids) + ): + raise SupportAuditSpecError("item_ids must be non-empty and unique") + if self.process_context not in {"main-process", "dataloader"}: + raise SupportAuditSpecError("process_context must be main-process or dataloader") + if self.process_context == "main-process" and self.multiprocessing_start_method is not None: + raise SupportAuditSpecError("main-process support audit does not accept multiprocessing_start_method") + if self.process_context == "dataloader" and self.multiprocessing_start_method not in { + "fork", + "forkserver", + "spawn", + }: + raise SupportAuditSpecError("dataloader support audit requires an explicit multiprocessing_start_method") + if _REVISION.fullmatch(self.runner_revision) is None: + raise SupportAuditSpecError("runner_revision must be a hexadecimal source revision") + + +@dataclass(frozen=True, slots=True) +class SupportAuditSpec: + audit_key: str + identity: SupportAuditIdentity + package_descriptor: Path + schema_version: str = SUPPORT_AUDIT_SPEC_VERSION + + @classmethod + def build( + cls, + *, + identity: SupportAuditIdentity, + package_descriptor: str | Path, + ) -> SupportAuditSpec: + return cls( + audit_key=_digest(identity), + identity=identity, + package_descriptor=Path(package_descriptor).resolve(), + ) + + def to_dict(self) -> dict[str, object]: + return { + **asdict(self.identity), + "audit_key": self.audit_key, + "item_ids": list(self.identity.item_ids), + "package_descriptor": str(self.package_descriptor), + "schema_version": self.schema_version, + } + + @classmethod + def from_dict(cls, document: dict[str, Any]) -> SupportAuditSpec: + if document.get("schema_version") != SUPPORT_AUDIT_SPEC_VERSION: + raise SupportAuditSpecError("unsupported support audit spec schema") + item_ids = document.get("item_ids") + if not isinstance(item_ids, list) or not all(isinstance(item, str) for item in item_ids): + raise SupportAuditSpecError("item_ids must be a string list") + identity = SupportAuditIdentity( + decoder_id=_required_string(document, "decoder_id"), + requested_threads=_optional_positive_int(document, "requested_threads"), + package_id=_required_string(document, "package_id"), + workload_id=_required_string(document, "workload_id"), + manifest_id=_required_string(document, "manifest_id"), + selection_id=_required_string(document, "selection_id"), + item_ids=tuple(item_ids), + process_context=_required_string(document, "process_context"), + multiprocessing_start_method=_optional_string(document, "multiprocessing_start_method"), + environment_id=_required_string(document, "environment_id"), + platform_id=_required_string(document, "platform_id"), + runner_revision=_required_string(document, "runner_revision"), + ) + spec = cls.build( + identity=identity, + package_descriptor=_required_string(document, "package_descriptor"), + ) + if document.get("audit_key") != spec.audit_key: + raise SupportAuditSpecError("audit_key does not match support audit identity") + return spec + + +def write_support_audit_spec(path: str | Path, spec: SupportAuditSpec) -> Path: + destination = Path(path) + destination.parent.mkdir(parents=True, exist_ok=True) + content = json.dumps(spec.to_dict(), ensure_ascii=False, indent=2, sort_keys=True) + "\n" + if destination.exists(): + if destination.read_text() != content: + raise SupportAuditSpecError(f"existing audit spec has different content: {destination}") + return destination + temporary: Path | None = None + try: + with tempfile.NamedTemporaryFile(mode="w", dir=destination.parent, prefix=".audit-spec.", delete=False) as file: + file.write(content) + temporary = Path(file.name) + temporary.rename(destination) + finally: + if temporary is not None: + temporary.unlink(missing_ok=True) + return destination + + +def load_support_audit_spec(path: str | Path) -> SupportAuditSpec: + try: + value = json.loads(Path(path).read_text()) + except (OSError, json.JSONDecodeError) as exc: + raise SupportAuditSpecError(f"cannot read support audit spec: {exc}") from exc + if not isinstance(value, dict): + raise SupportAuditSpecError("support audit spec must be a JSON object") + return SupportAuditSpec.from_dict(value) + + +def _digest(identity: SupportAuditIdentity) -> str: + payload = {**asdict(identity), "schema_version": SUPPORT_AUDIT_SPEC_VERSION} + canonical = json.dumps(payload, ensure_ascii=False, separators=(",", ":"), sort_keys=True) + return hashlib.sha256(canonical.encode()).hexdigest() + + +def _required_string(payload: dict[str, Any], key: str) -> str: + value = payload.get(key) + if not isinstance(value, str) or not value: + raise SupportAuditSpecError(f"field {key!r} must be a non-empty string") + return value + + +def _optional_positive_int(payload: dict[str, Any], key: str) -> int | None: + value = payload.get(key) + if value is None: + return None + if isinstance(value, bool) or not isinstance(value, int) or value <= 0: + raise SupportAuditSpecError(f"field {key!r} must be a positive integer or null") + return value + + +def _optional_string(payload: dict[str, Any], key: str) -> str | None: + value = payload.get(key) + if value is None: + return None + if not isinstance(value, str) or not value: + raise SupportAuditSpecError(f"field {key!r} must be a non-empty string or null") + return value diff --git a/imread_benchmark/support/store.py b/imread_benchmark/support/store.py new file mode 100644 index 0000000..4bc6dd6 --- /dev/null +++ b/imread_benchmark/support/store.py @@ -0,0 +1,212 @@ +from __future__ import annotations + +import json +import os +import tempfile +from pathlib import Path +from typing import Any + +from imread_benchmark.support.audit import ( + SUPPORT_SCHEMA_VERSION, + SupportAudit, + SupportFailure, + SupportSet, + SupportSetIdentity, + _digest, +) + + +def write_support_audit(root: str | Path, audit: SupportAudit) -> Path: + path = Path(root) / "audits" / f"{audit.audit_id}.json" + _write_content_addressed(path, audit.to_dict()) + if load_support_audit(path) != audit: + raise ValueError("persisted support audit differs from input") + return path + + +def write_support_set(root: str | Path, support_set: SupportSet) -> Path: + path = Path(root) / "sets" / f"{support_set.support_set_id}.json" + _write_content_addressed(path, support_set.to_dict()) + if load_support_set(path) != support_set: + raise ValueError("persisted support set differs from input") + return path + + +def commit_support_audit(root: str | Path, audit_key: str, audit: SupportAudit) -> Path: + root_path = Path(root) + audit_path = write_support_audit(root_path, audit) + marker = root_path / "keys" / f"{audit_key}.json" + _write_content_addressed( + marker, + { + "audit_id": audit.audit_id, + "audit_key": audit_key, + "schema_version": SUPPORT_SCHEMA_VERSION, + "status": "committed", + }, + ) + if load_committed_support_audit(root_path, audit_key) != audit: + raise ValueError("committed support audit differs from input") + return audit_path + + +def load_committed_support_audit(root: str | Path, audit_key: str) -> SupportAudit: + root_path = Path(root) + marker_path = root_path / "keys" / f"{audit_key}.json" + marker = _read_object(marker_path) + if ( + marker.get("schema_version") != SUPPORT_SCHEMA_VERSION + or marker.get("status") != "committed" + or marker.get("audit_key") != audit_key + ): + raise ValueError("invalid support audit commit marker") + audit_id = _required_string(marker, "audit_id") + return load_support_audit(root_path / "audits" / f"{audit_id}.json") + + +def load_support_audit(path: str | Path) -> SupportAudit: + source = Path(path) + document = _read_object(source) + if document.get("schema_version") != SUPPORT_SCHEMA_VERSION: + raise ValueError("unsupported support audit schema") + audit_id = _required_string(document, "audit_id") + identity = {key: value for key, value in document.items() if key != "audit_id"} + if _digest(identity) != audit_id or source.stem != audit_id: + raise ValueError("support audit audit_id does not match its content or filename") + raw_failures = document.get("failures") + if not isinstance(raw_failures, list): + raise TypeError("support audit failures must be a list") + failures = tuple(_failure(value) for value in raw_failures) + return SupportAudit( + audit_id=audit_id, + decoder_id=_required_string(document, "decoder_id"), + requested_threads=_optional_positive_int(document, "requested_threads"), + manifest_id=_required_string(document, "manifest_id"), + selection_id=_required_string(document, "selection_id"), + process_context=_required_string(document, "process_context"), + multiprocessing_start_method=_optional_string(document, "multiprocessing_start_method"), + output_contract=_required_string(document, "output_contract"), + environment_id=_required_string(document, "environment_id"), + platform_id=_required_string(document, "platform_id"), + successful_item_ids=_string_tuple(document, "successful_item_ids", allow_empty=True), + failures=failures, + ) + + +def load_support_set(path: str | Path) -> SupportSet: + source = Path(path) + document = _read_object(source) + if document.get("schema_version") != SUPPORT_SCHEMA_VERSION: + raise ValueError("unsupported support set schema") + support_set_id = _required_string(document, "support_set_id") + policy = _required_string(document, "policy") + if policy not in {"common", "operational"}: + raise ValueError(f"unsupported support set policy: {policy!r}") + audit_ids = _string_tuple(document, "audit_ids") + item_ids = _string_tuple(document, "item_ids") + manifest_id = _required_string(document, "manifest_id") + selection_id = _required_string(document, "selection_id") + process_context = _required_string(document, "process_context") + multiprocessing_start_method = _optional_string(document, "multiprocessing_start_method") + identity = SupportSetIdentity( + policy=policy, + manifest_id=manifest_id, + selection_id=selection_id, + process_context=process_context, + multiprocessing_start_method=multiprocessing_start_method, + audit_ids=audit_ids, + item_ids=item_ids, + ).to_dict() + if _digest(identity) != support_set_id or source.stem != support_set_id: + raise ValueError("support set support_set_id does not match its content or filename") + return SupportSet( + support_set_id=support_set_id, + policy=policy, + manifest_id=manifest_id, + selection_id=selection_id, + process_context=process_context, + multiprocessing_start_method=multiprocessing_start_method, + audit_ids=audit_ids, + item_ids=item_ids, + ) + + +def _write_content_addressed(path: Path, payload: dict[str, object]) -> None: + path.parent.mkdir(parents=True, exist_ok=True) + content = json.dumps(payload, ensure_ascii=False, indent=2, sort_keys=True) + "\n" + if path.exists(): + if path.read_text() != content: + raise ValueError(f"content-addressed artifact differs from existing file: {path}") + return + temporary: Path | None = None + try: + with tempfile.NamedTemporaryFile(mode="w", dir=path.parent, prefix=f".{path.name}.", delete=False) as file: + file.write(content) + file.flush() + os.fsync(file.fileno()) + temporary = Path(file.name) + try: + os.link(temporary, path) + except FileExistsError: + if path.read_text() != content: + raise ValueError(f"content-addressed artifact differs from concurrent writer: {path}") from None + finally: + if temporary is not None: + temporary.unlink(missing_ok=True) + + +def _read_object(path: Path) -> dict[str, Any]: + try: + value = json.loads(path.read_text()) + except (OSError, json.JSONDecodeError) as exc: + raise ValueError(f"cannot read support artifact {path}: {exc}") from exc + if not isinstance(value, dict): + raise TypeError("support artifact must be a JSON object") + return value + + +def _required_string(payload: dict[str, Any], key: str) -> str: + value = payload.get(key) + if not isinstance(value, str) or not value: + raise ValueError(f"support artifact field {key!r} must be a non-empty string") + return value + + +def _optional_string(payload: dict[str, Any], key: str) -> str | None: + value = payload.get(key) + if value is None: + return None + if not isinstance(value, str) or not value: + raise ValueError(f"support artifact field {key!r} must be a non-empty string or null") + return value + + +def _optional_positive_int(payload: dict[str, Any], key: str) -> int | None: + value = payload.get(key) + if value is None: + return None + if isinstance(value, bool) or not isinstance(value, int) or value <= 0: + raise ValueError(f"support artifact field {key!r} must be a positive integer or null") + return value + + +def _string_tuple(payload: dict[str, Any], key: str, *, allow_empty: bool = False) -> tuple[str, ...]: + value = payload.get(key) + if not isinstance(value, list) or not all(isinstance(item, str) and item for item in value): + raise ValueError(f"support artifact field {key!r} must be a string list") + if not allow_empty and not value: + raise ValueError(f"support artifact field {key!r} must not be empty") + result = tuple(value) + if len(set(result)) != len(result): + raise ValueError(f"support artifact field {key!r} must contain unique values") + return result + + +def _failure(value: object) -> SupportFailure: + if not isinstance(value, dict): + raise TypeError("support audit failure must be an object") + return SupportFailure( + item_id=_required_string(value, "item_id"), + failure_type=_required_string(value, "failure_type"), + message=_required_string(value, "message"), + ) diff --git a/imread_benchmark/support/worker.py b/imread_benchmark/support/worker.py new file mode 100644 index 0000000..94e56b8 --- /dev/null +++ b/imread_benchmark/support/worker.py @@ -0,0 +1,75 @@ +from __future__ import annotations + +import argparse +import json +import os +from pathlib import Path + +from imread_benchmark.datasets.package import open_dataset_package +from imread_benchmark.execution.measurement import configure_decoder +from imread_benchmark.support.audit import SupportAuditContext, audit_decoder_support +from imread_benchmark.support.dataloader import audit_decoder_support_in_dataloader +from imread_benchmark.support.spec import load_support_audit_spec +from imread_benchmark.support.store import commit_support_audit + + +def run_support_audit_worker(spec_path: str | Path, artifact_root: str | Path) -> dict[str, object]: + spec = load_support_audit_spec(spec_path) + identity = spec.identity + package = open_dataset_package( + spec.package_descriptor, + trust_ready=(spec.package_descriptor.parent / ".READY.json").is_file(), + ) + if package.descriptor.get("package_id") != identity.package_id: + raise ValueError("support audit package_id does not match descriptor") + workload = package.descriptor.get("workloads", {}).get(identity.workload_id) + if not isinstance(workload, dict) or workload.get("manifest_id") != identity.manifest_id: + raise ValueError("support audit manifest_id does not match descriptor") + all_items = package.read_workload_items(identity.workload_id) + by_id = {item.item_id: item for item in all_items} + if len(by_id) != len(all_items) or any(item_id not in by_id for item_id in identity.item_ids): + raise ValueError("support audit selection does not match workload items") + items = tuple(by_id[item_id] for item_id in identity.item_ids) + context = SupportAuditContext( + manifest_id=identity.manifest_id, + selection_id=identity.selection_id, + process_context=identity.process_context, + multiprocessing_start_method=identity.multiprocessing_start_method, + requested_threads=identity.requested_threads, + environment_id=identity.environment_id, + platform_id=identity.platform_id, + ) + if identity.process_context == "dataloader": + audit = audit_decoder_support_in_dataloader( + identity.decoder_id, + items, + context, + requested_threads=identity.requested_threads, + ).audit + else: + from imread_benchmark.decoders import REGISTRY + + decoder_class = REGISTRY.get(identity.decoder_id) + if decoder_class is None: + raise ValueError(f"unknown decoder {identity.decoder_id!r}") + decoder = decoder_class() + configure_decoder(decoder, identity.requested_threads) + audit = audit_decoder_support(decoder, items, context) + commit_support_audit(Path(artifact_root) / "support", spec.audit_key, audit) + return { + "audit_id": audit.audit_id, + "audit_key": spec.audit_key, + "process_id": os.getpid(), + } + + +def main() -> None: + parser = argparse.ArgumentParser() + parser.add_argument("--audit-spec", required=True, type=Path) + parser.add_argument("--artifact-root", required=True, type=Path) + args = parser.parse_args() + print(json.dumps(run_support_audit_worker(args.audit_spec, args.artifact_root), sort_keys=True)) + + +if __name__ == "__main__": + main() diff --git a/imread_benchmark/utils.py b/imread_benchmark/utils.py deleted file mode 100644 index cbf90f6..0000000 --- a/imread_benchmark/utils.py +++ /dev/null @@ -1,76 +0,0 @@ -from __future__ import annotations - -import functools -import logging -import multiprocessing -import platform -import sys -from importlib.metadata import PackageNotFoundError, version -from pathlib import Path - -import cpuinfo - -logger = logging.getLogger(__name__) - - -@functools.lru_cache(maxsize=1) -def _raw_cpuinfo() -> dict[str, object]: - # ~100 ms per call; called by both get_cpu_info() and get_system_identifier() - # on every benchmark process, so cache it. - try: - return cpuinfo.get_cpu_info() - except Exception as exc: - logger.warning("Failed to get CPU info: %s", exc) - return {"error": str(exc)} - - -def get_cpu_info() -> dict[str, object]: - info = _raw_cpuinfo() - if "error" in info: - return {"error": info["error"]} - return { - "brand_raw": info.get("brand_raw", "Unknown"), - "arch": info.get("arch", "Unknown"), - "hz_advertised_raw": info.get("hz_advertised_raw", "Unknown"), - "count": multiprocessing.cpu_count(), - } - - -def get_system_identifier() -> str: - info = _raw_cpuinfo() - if "error" in info: - return "unknown-system" - cpu_brand = str(info.get("brand_raw", "Unknown")) - os_id = "darwin" if platform.system().lower() == "darwin" else "linux" - cpu_id = cpu_brand.replace(" ", "-") - return f"{os_id}_{cpu_id}" - - -def get_package_versions(library_name: str | None = None) -> dict[str, object]: - versions: dict[str, object] = { - "Python": sys.version.split()[0], - "OS": platform.system(), - "OS Version": platform.version(), - "Machine": platform.machine(), - "CPU": get_cpu_info(), - } - - if library_name: - from imread_benchmark.decoders import REGISTRY - - decoder_cls = REGISTRY.get(library_name) - if decoder_cls is not None: - pkg = decoder_cls.package_name - try: - versions[library_name] = version(pkg) - except PackageNotFoundError: - versions[library_name] = f"not installed ({pkg})" - except Exception as exc: - versions[library_name] = f"error: {exc}" - - return versions - - -def collect_jpeg_paths(data_dir: str | Path, num_images: int) -> list[Path]: - extensions = {".jpg", ".jpeg"} - return sorted(p for p in Path(data_dir).rglob("*") if p.suffix.lower() in extensions)[:num_images] diff --git a/output/linux_AMD-EPYC-9B14/ajpegli_1t_results.json b/output/linux_AMD-EPYC-9B14/ajpegli_1t_results.json deleted file mode 100644 index 5f23e40..0000000 --- a/output/linux_AMD-EPYC-9B14/ajpegli_1t_results.json +++ /dev/null @@ -1,57 +0,0 @@ -{ - "library": "ajpegli", - "mode": "memory", - "run_tag": "1t", - "requested_threads": 1, - "effective_threads": 1, - "system_info": { - "Python": "3.12.3", - "OS": "Linux", - "OS Version": "#17~24.04.1-Ubuntu SMP Thu May 7 15:12:38 UTC 2026", - "Machine": "x86_64", - "CPU": { - "brand_raw": "AMD EPYC 9B14", - "arch": "X86_64", - "hz_advertised_raw": "Unknown", - "count": 16 - }, - "ajpegli": "1.0.0" - }, - "benchmark_results": { - "images_per_second_mean": 400.17318636034486, - "images_per_second_std": 0.0418143320963244, - "images_per_second_p50": 400.1734736537274, - "images_per_second_p90": 400.22015594016983, - "images_per_second_p99": 400.2213877410496, - "us_per_image_mean": 2498.91808097762, - "us_per_image_p50": 2498.9162596651927, - "us_per_image_p90": 2499.213334074683, - "us_per_image_p99": 2499.224782008442, - "raw_times_s": [ - 124.929381337, - 124.95721351500003, - 124.94331406699996, - 124.95880347400009, - 124.92831326099997 - ], - "raw_throughput_ips": [ - 400.21810293870345, - 400.1289608942668, - 400.1734736537274, - 400.12386970721263, - 400.22152460781405 - ], - "num_images_total": 50000, - "num_images_decoded": 49999, - "num_images_skipped": 1, - "skip_rate": 2e-05, - "skip_indices": [ - 19876 - ], - "skip_examples": [ - "idx=19876: DecodeError: jpegli decode failed: ./lib/jpegli/color_transform.cc:549: Unsupported color transform 4 -> 2" - ] - }, - "num_images": 50000, - "num_runs": 5 -} \ No newline at end of file diff --git a/output/linux_AMD-EPYC-9B14/ajpegli_dataloader_results.json b/output/linux_AMD-EPYC-9B14/ajpegli_dataloader_results.json deleted file mode 100644 index a7f585d..0000000 --- a/output/linux_AMD-EPYC-9B14/ajpegli_dataloader_results.json +++ /dev/null @@ -1,125 +0,0 @@ -{ - "library": "ajpegli", - "benchmark_type": "dataloader", - "batch_size": 32, - "num_threads": 1, - "system_info": { - "Python": "3.12.3", - "OS": "Linux", - "OS Version": "#17~24.04.1-Ubuntu SMP Thu May 7 15:12:38 UTC 2026", - "Machine": "x86_64", - "CPU": { - "brand_raw": "AMD EPYC 9B14", - "arch": "X86_64", - "hz_advertised_raw": "Unknown", - "count": 16 - }, - "ajpegli": "1.0.0" - }, - "worker_results": [ - { - "num_workers": 0, - "num_warmup": 1, - "images_per_second_mean": 395.540106984955, - "images_per_second_std": 0.08917249239482852, - "images_per_second_p50": 395.5006375171751, - "images_per_second_p90": 395.6309822101358, - "images_per_second_p99": 395.66030976605197, - "us_per_image_mean": 2528.188755475108, - "us_per_image_p50": 2528.4409306586103, - "us_per_image_p90": 2528.668662461249, - "us_per_image_p99": 2528.7199021168426, - "raw_times_s": [ - 126.43375104500001, - 126.41951809199986, - 126.36745961799988 - ], - "raw_throughput_ips": [ - 395.4561150543139, - 395.5006375171751, - 395.663568383376 - ] - }, - { - "num_workers": 2, - "num_warmup": 1, - "images_per_second_mean": 704.4197781175443, - "images_per_second_std": 0.09802237689406454, - "images_per_second_p50": 704.4847162356363, - "images_per_second_p90": 704.491642780342, - "images_per_second_p99": 704.4932012529007, - "us_per_image_mean": 1419.608095101902, - "us_per_image_p50": 1419.4772107242134, - "us_per_image_p90": 1419.8052894617902, - "us_per_image_p99": 1419.879107177745, - "raw_times_s": [ - 70.99294557000007, - 70.97156881199999, - 70.97244105899995 - ], - "raw_throughput_ips": [ - 704.2812437004781, - 704.4933744165183, - 704.4847162356363 - ] - }, - { - "num_workers": 4, - "num_warmup": 1, - "images_per_second_mean": 1258.8811499873761, - "images_per_second_std": 4.412095443803712, - "images_per_second_p50": 1261.3187124413855, - "images_per_second_p90": 1262.3730723324231, - "images_per_second_p99": 1262.6103033079066, - "us_per_image_mean": 794.3659432055289, - "us_per_image_p50": 792.8210294005852, - "us_per_image_p90": 797.1908646852931, - "us_per_image_p99": 798.1740776243524, - "raw_times_s": [ - 39.913367892, - 39.64025864899986, - 39.598881841999855 - ], - "raw_throughput_ips": [ - 1252.6880752155598, - 1261.3187124413855, - 1262.6366623051827 - ] - }, - { - "num_workers": 8, - "num_warmup": 1, - "images_per_second_mean": 1458.2945506521744, - "images_per_second_std": 67.87134268899736, - "images_per_second_p50": 1505.1164847873945, - "images_per_second_p90": 1506.9814989029114, - "images_per_second_p99": 1507.4011270789026, - "us_per_image_mean": 687.2718519970394, - "us_per_image_p50": 664.4004036280655, - "us_per_image_p90": 720.1138757855215, - "us_per_image_p99": 732.6494070209492, - "raw_times_s": [ - 36.70137814900045, - 33.21935578099965, - 33.16798205399982 - ], - "raw_throughput_ips": [ - 1362.3194147373376, - 1505.1164847873945, - 1507.4477524317906 - ] - } - ], - "num_images": 49999, - "num_images_total": 50000, - "num_images_decoded": 49999, - "num_images_skipped": 1, - "skip_rate": 2e-05, - "skip_indices": [ - 19876 - ], - "skip_examples": [ - "idx=19876: DecodeError: jpegli decode failed: ./lib/jpegli/color_transform.cc:549: Unsupported color transform 4 -> 2" - ], - "num_runs": 3 -} \ No newline at end of file diff --git a/output/linux_AMD-EPYC-9B14/imagecodecs_1t_results.json b/output/linux_AMD-EPYC-9B14/imagecodecs_1t_results.json deleted file mode 100644 index ff530f1..0000000 --- a/output/linux_AMD-EPYC-9B14/imagecodecs_1t_results.json +++ /dev/null @@ -1,53 +0,0 @@ -{ - "library": "imagecodecs", - "mode": "memory", - "run_tag": "1t", - "requested_threads": 1, - "effective_threads": 1, - "system_info": { - "Python": "3.12.3", - "OS": "Linux", - "OS Version": "#12~24.04.1-Ubuntu SMP Fri Mar 27 23:35:04 UTC 2026", - "Machine": "x86_64", - "CPU": { - "brand_raw": "AMD EPYC 9B14", - "arch": "X86_64", - "hz_advertised_raw": "Unknown", - "count": 16 - }, - "imagecodecs": "2026.3.6" - }, - "benchmark_results": { - "images_per_second_mean": 676.5651438043105, - "images_per_second_std": 0.04953022399347552, - "images_per_second_p50": 676.5489503321108, - "images_per_second_p90": 676.6233065556374, - "images_per_second_p99": 676.6306400185921, - "us_per_image_mean": 1478.054278316, - "us_per_image_p50": 1478.08964822, - "us_per_image_p90": 1478.1683862000002, - "us_per_image_p99": 1478.201436972001, - "raw_times_s": [ - 73.89547092699999, - 73.904482411, - 73.897695698, - 73.91025546400004, - 73.90566507899996 - ], - "raw_throughput_ips": [ - 676.6314548478093, - 676.5489503321108, - 676.6110841173795, - 676.4961057989284, - 676.5381239253245 - ], - "num_images_total": 50000, - "num_images_decoded": 50000, - "num_images_skipped": 0, - "skip_rate": 0.0, - "skip_indices": [], - "skip_examples": [] - }, - "num_images": 50000, - "num_runs": 5 -} \ No newline at end of file diff --git a/output/linux_AMD-EPYC-9B14/imagecodecs_dataloader_results.json b/output/linux_AMD-EPYC-9B14/imagecodecs_dataloader_results.json deleted file mode 100644 index d9b8a47..0000000 --- a/output/linux_AMD-EPYC-9B14/imagecodecs_dataloader_results.json +++ /dev/null @@ -1,121 +0,0 @@ -{ - "library": "imagecodecs", - "benchmark_type": "dataloader", - "batch_size": 32, - "num_threads": 1, - "system_info": { - "Python": "3.12.3", - "OS": "Linux", - "OS Version": "#12~24.04.1-Ubuntu SMP Fri Mar 27 23:35:04 UTC 2026", - "Machine": "x86_64", - "CPU": { - "brand_raw": "AMD EPYC 9B14", - "arch": "X86_64", - "hz_advertised_raw": "Unknown", - "count": 16 - }, - "imagecodecs": "2026.3.6" - }, - "worker_results": [ - { - "num_workers": 0, - "num_warmup": 1, - "images_per_second_mean": 670.602760216846, - "images_per_second_std": 0.1566747235091943, - "images_per_second_p50": 670.5248477317151, - "images_per_second_p90": 670.7620483841305, - "images_per_second_p99": 670.8154185309239, - "us_per_image_mean": 1491.1958522999992, - "us_per_image_p50": 1491.369042299998, - "us_per_image_p90": 1491.4807304439992, - "us_per_image_p99": 1491.5058602763995, - "raw_times_s": [ - 74.5684521149999, - 74.57543262399997, - 74.53549310599999 - ], - "raw_throughput_ips": [ - 670.5248477317151, - 670.4620843715887, - 670.8213485472343 - ] - }, - { - "num_workers": 2, - "num_warmup": 1, - "images_per_second_mean": 1110.733327353067, - "images_per_second_std": 3.262460821615361, - "images_per_second_p50": 1112.9151449535398, - "images_per_second_p90": 1113.1135126303557, - "images_per_second_p99": 1113.1581453576393, - "us_per_image_mean": 900.3138922266672, - "us_per_image_p50": 898.5411012999975, - "us_per_image_p90": 902.9559211720007, - "us_per_image_p99": 903.9492556432014, - "raw_times_s": [ - 44.91704746200014, - 44.92705506499988, - 45.20298130700007 - ], - "raw_throughput_ips": [ - 1113.1631045495596, - 1112.9151449535398, - 1106.1217325561017 - ] - }, - { - "num_workers": 4, - "num_warmup": 1, - "images_per_second_mean": 1543.337819098903, - "images_per_second_std": 9.193342649982888, - "images_per_second_p50": 1548.0248478220142, - "images_per_second_p90": 1550.8023234202228, - "images_per_second_p99": 1551.42725542982, - "us_per_image_mean": 647.9693600000004, - "us_per_image_p50": 645.98446298, - "us_per_image_p90": 651.9046514759984, - "us_per_image_p99": 653.2366938875981, - "raw_times_s": [ - 32.22694592100015, - 32.6692349299999, - 32.299223149 - ], - "raw_throughput_ips": [ - 1551.496692319775, - 1530.491917154919, - 1548.0248478220142 - ] - }, - { - "num_workers": 8, - "num_warmup": 1, - "images_per_second_mean": 1448.9206837379315, - "images_per_second_std": 12.25459689090077, - "images_per_second_p50": 1443.2765416736227, - "images_per_second_p90": 1461.4019170119636, - "images_per_second_p99": 1465.4801264630903, - "us_per_image_mean": 690.2180315066653, - "us_per_image_p50": 692.867909319998, - "us_per_image_p90": 695.0750938799983, - "us_per_image_p99": 695.5717104059984, - "raw_times_s": [ - 34.6433954659999, - 34.78134450099992, - 34.10796475899997 - ], - "raw_throughput_ips": [ - 1443.2765416736227, - 1437.5522486936225, - 1465.9332608465488 - ] - } - ], - "num_images": 50000, - "num_images_total": 50000, - "num_images_decoded": 50000, - "num_images_skipped": 0, - "skip_rate": 0.0, - "skip_indices": [], - "skip_examples": [], - "num_runs": 3 -} \ No newline at end of file diff --git a/output/linux_AMD-EPYC-9B14/imageio_1t_results.json b/output/linux_AMD-EPYC-9B14/imageio_1t_results.json deleted file mode 100644 index 5b03161..0000000 --- a/output/linux_AMD-EPYC-9B14/imageio_1t_results.json +++ /dev/null @@ -1,53 +0,0 @@ -{ - "library": "imageio", - "mode": "memory", - "run_tag": "1t", - "requested_threads": 1, - "effective_threads": 1, - "system_info": { - "Python": "3.12.3", - "OS": "Linux", - "OS Version": "#12~24.04.1-Ubuntu SMP Fri Mar 27 23:35:04 UTC 2026", - "Machine": "x86_64", - "CPU": { - "brand_raw": "AMD EPYC 9B14", - "arch": "X86_64", - "hz_advertised_raw": "Unknown", - "count": 16 - }, - "imageio": "2.37.3" - }, - "benchmark_results": { - "images_per_second_mean": 496.01600199760935, - "images_per_second_std": 0.7712302525513154, - "images_per_second_p50": 496.2241376533611, - "images_per_second_p90": 496.63893789524855, - "images_per_second_p99": 496.69528250894825, - "us_per_image_mean": 2016.068872836, - "us_per_image_p50": 2015.218374360002, - "us_per_image_p90": 2019.5874676839996, - "us_per_image_p99": 2021.8310383863989, - "raw_times_s": [ - 100.76091871800008, - 101.10401620099992, - 100.66407222299995, - 100.79240915900004, - 100.69580190800002 - ], - "raw_throughput_ips": [ - 496.2241376533611, - 494.5401961144398, - 496.70154302158153, - 496.0691029929148, - 496.54503020574913 - ], - "num_images_total": 50000, - "num_images_decoded": 50000, - "num_images_skipped": 0, - "skip_rate": 0.0, - "skip_indices": [], - "skip_examples": [] - }, - "num_images": 50000, - "num_runs": 5 -} \ No newline at end of file diff --git a/output/linux_AMD-EPYC-9B14/imageio_dataloader_results.json b/output/linux_AMD-EPYC-9B14/imageio_dataloader_results.json deleted file mode 100644 index 8883e70..0000000 --- a/output/linux_AMD-EPYC-9B14/imageio_dataloader_results.json +++ /dev/null @@ -1,121 +0,0 @@ -{ - "library": "imageio", - "benchmark_type": "dataloader", - "batch_size": 32, - "num_threads": 1, - "system_info": { - "Python": "3.12.3", - "OS": "Linux", - "OS Version": "#12~24.04.1-Ubuntu SMP Fri Mar 27 23:35:04 UTC 2026", - "Machine": "x86_64", - "CPU": { - "brand_raw": "AMD EPYC 9B14", - "arch": "X86_64", - "hz_advertised_raw": "Unknown", - "count": 16 - }, - "imageio": "2.37.3" - }, - "worker_results": [ - { - "num_workers": 0, - "num_warmup": 1, - "images_per_second_mean": 482.7624910137792, - "images_per_second_std": 3.4170653978697407, - "images_per_second_p50": 483.295780018339, - "images_per_second_p90": 485.98341068875584, - "images_per_second_p99": 486.58812758959965, - "us_per_image_mean": 2071.5159198733304, - "us_per_image_p50": 2069.1262811399974, - "us_per_image_p90": 2086.2884899879973, - "us_per_image_p99": 2090.149986978797, - "raw_times_s": [ - 104.52895210999986, - 103.45631405699987, - 102.7421218139998 - ], - "raw_throughput_ips": [ - 478.33637466663845, - 483.295780018339, - 486.65531835636006 - ] - }, - { - "num_workers": 2, - "num_warmup": 1, - "images_per_second_mean": 831.2606204918847, - "images_per_second_std": 2.8332251078321113, - "images_per_second_p50": 830.266855645967, - "images_per_second_p90": 834.1486196413338, - "images_per_second_p99": 835.0220165402912, - "us_per_image_mean": 1203.0061009266713, - "us_per_image_p50": 1204.4320367600085, - "us_per_image_p90": 1206.6081792240075, - "us_per_image_p99": 1207.0978112784073, - "raw_times_s": [ - 60.221601838000424, - 59.871702558999914, - 60.35761074200036 - ], - "raw_throughput_ips": [ - 830.266855645967, - 835.1190606401755, - 828.3959451895115 - ] - }, - { - "num_workers": 4, - "num_warmup": 1, - "images_per_second_mean": 1273.033501106702, - "images_per_second_std": 1.930658293994749, - "images_per_second_p50": 1272.035914260243, - "images_per_second_p90": 1274.9938869044843, - "images_per_second_p99": 1275.6594307494386, - "us_per_image_mean": 785.5270870199971, - "us_per_image_p50": 786.1413257200002, - "us_per_image_p90": 786.4899353839974, - "us_per_image_p99": 786.5683725583967, - "raw_times_s": [ - 39.30706628600001, - 39.32885438999983, - 39.19314237699973 - ], - "raw_throughput_ips": [ - 1272.035914260243, - 1271.3312089943186, - 1275.7333800655447 - ] - }, - { - "num_workers": 8, - "num_warmup": 1, - "images_per_second_mean": 1234.2800305725266, - "images_per_second_std": 20.960958571810913, - "images_per_second_p50": 1233.979880876117, - "images_per_second_p90": 1254.8764689065565, - "images_per_second_p99": 1259.5782012134055, - "us_per_image_mean": 810.4225901133365, - "us_per_image_p50": 810.3859840000041, - "us_per_image_p90": 823.9126877920025, - "us_per_image_p99": 826.9561961452022, - "raw_times_s": [ - 39.67937113000016, - 41.36471818700011, - 40.519299200000205 - ], - "raw_throughput_ips": [ - 1260.1006159141664, - 1208.759594927296, - 1233.979880876117 - ] - } - ], - "num_images": 50000, - "num_images_total": 50000, - "num_images_decoded": 50000, - "num_images_skipped": 0, - "skip_rate": 0.0, - "skip_indices": [], - "skip_examples": [], - "num_runs": 3 -} \ No newline at end of file diff --git a/output/linux_AMD-EPYC-9B14/jpeg4py_1t_results.json b/output/linux_AMD-EPYC-9B14/jpeg4py_1t_results.json deleted file mode 100644 index 3d472fa..0000000 --- a/output/linux_AMD-EPYC-9B14/jpeg4py_1t_results.json +++ /dev/null @@ -1,57 +0,0 @@ -{ - "library": "jpeg4py", - "mode": "memory", - "run_tag": "1t", - "requested_threads": 1, - "effective_threads": 1, - "system_info": { - "Python": "3.12.3", - "OS": "Linux", - "OS Version": "#12~24.04.1-Ubuntu SMP Fri Mar 27 23:35:04 UTC 2026", - "Machine": "x86_64", - "CPU": { - "brand_raw": "AMD EPYC 9B14", - "arch": "X86_64", - "hz_advertised_raw": "Unknown", - "count": 16 - }, - "jpeg4py": "0.1.4" - }, - "benchmark_results": { - "images_per_second_mean": 636.2763153143356, - "images_per_second_std": 0.13414051651095607, - "images_per_second_p50": 636.2154541774977, - "images_per_second_p90": 636.4410854500637, - "images_per_second_p99": 636.4750981863112, - "us_per_image_mean": 1571.644300398006, - "us_per_image_p50": 1571.794575931521, - "us_per_image_p90": 1571.9697458829144, - "us_per_image_p99": 1572.0308011168167, - "raw_times_s": [ - 78.59182749000001, - 78.58815700200012, - 78.55563126599964, - 78.56729390600003, - 78.60030721399971 - ], - "raw_throughput_ips": [ - 636.185740894775, - 636.2154541774977, - 636.4788773792276, - 636.3843975563179, - 636.1171065638601 - ], - "num_images_total": 50000, - "num_images_decoded": 49999, - "num_images_skipped": 1, - "skip_rate": 2e-05, - "skip_indices": [ - 19876 - ], - "skip_examples": [ - "idx=19876: JPEGRuntimeError: tjDecompress2() failed with error -1 and error string Unsupported color conversion request" - ] - }, - "num_images": 50000, - "num_runs": 5 -} \ No newline at end of file diff --git a/output/linux_AMD-EPYC-9B14/jpeg4py_dataloader_results.json b/output/linux_AMD-EPYC-9B14/jpeg4py_dataloader_results.json deleted file mode 100644 index f61ca5f..0000000 --- a/output/linux_AMD-EPYC-9B14/jpeg4py_dataloader_results.json +++ /dev/null @@ -1,125 +0,0 @@ -{ - "library": "jpeg4py", - "benchmark_type": "dataloader", - "batch_size": 32, - "num_threads": 1, - "system_info": { - "Python": "3.12.3", - "OS": "Linux", - "OS Version": "#12~24.04.1-Ubuntu SMP Fri Mar 27 23:35:04 UTC 2026", - "Machine": "x86_64", - "CPU": { - "brand_raw": "AMD EPYC 9B14", - "arch": "X86_64", - "hz_advertised_raw": "Unknown", - "count": 16 - }, - "jpeg4py": "0.1.4" - }, - "worker_results": [ - { - "num_workers": 0, - "num_warmup": 1, - "images_per_second_mean": 631.5444478956379, - "images_per_second_std": 0.35705681914024495, - "images_per_second_p50": 631.4047625730117, - "images_per_second_p90": 631.9085757308861, - "images_per_second_p99": 632.0219336914079, - "us_per_image_mean": 1583.420332299981, - "us_per_image_p50": 1583.770125402509, - "us_per_image_p90": 1584.1930913178282, - "us_per_image_p99": 1584.2882586487751, - "raw_times_s": [ - 79.10801974300011, - 79.21335734100012, - 79.18692250000004 - ], - "raw_throughput_ips": [ - 632.0345290203547, - 631.1940520935473, - 631.4047625730117 - ] - }, - { - "num_workers": 2, - "num_warmup": 1, - "images_per_second_mean": 1021.5785636821097, - "images_per_second_std": 2.3282954254426786, - "images_per_second_p50": 1021.4986072361061, - "images_per_second_p90": 1023.8751365963896, - "images_per_second_p99": 1024.4098557024533, - "us_per_image_mean": 978.8823193330549, - "us_per_image_p50": 978.953855557106, - "us_per_image_p90": 981.0531128542543, - "us_per_image_p99": 981.5254457461126, - "raw_times_s": [ - 48.80478264800058, - 49.07791478099989, - 48.946713823999744 - ], - "raw_throughput_ips": [ - 1024.4692689364604, - 1018.7678148737627, - 1021.4986072361061 - ] - }, - { - "num_workers": 4, - "num_warmup": 1, - "images_per_second_mean": 1443.2080568974804, - "images_per_second_std": 16.204211295389346, - "images_per_second_p50": 1444.3062734340808, - "images_per_second_p90": 1458.846991299652, - "images_per_second_p99": 1462.1186528194055, - "us_per_image_mean": 692.9882552584404, - "us_per_image_p50": 692.3739226184568, - "us_per_image_p90": 700.7322434168709, - "us_per_image_p99": 702.612865596514, - "raw_times_s": [ - 34.18776720799997, - 34.618003757000224, - 35.1403883590001 - ], - "raw_throughput_ips": [ - 1462.4821707660449, - 1444.3062734340808, - 1422.8357264923152 - ] - }, - { - "num_workers": 8, - "num_warmup": 1, - "images_per_second_mean": 1352.7975432700812, - "images_per_second_std": 9.480507838483843, - "images_per_second_p50": 1355.9116590879785, - "images_per_second_p90": 1361.209648075414, - "images_per_second_p99": 1362.4016955975871, - "us_per_image_mean": 739.2453358067147, - "us_per_image_p50": 737.5111743434865, - "us_per_image_p90": 744.540852249047, - "us_per_image_p99": 746.1225297777981, - "raw_times_s": [ - 37.314167288000135, - 36.87482120599998, - 36.695594140999674 - ], - "raw_throughput_ips": [ - 1339.9468253999917, - 1355.9116590879785, - 1362.534145322273 - ] - } - ], - "num_images": 49999, - "num_images_total": 50000, - "num_images_decoded": 49999, - "num_images_skipped": 1, - "skip_rate": 2e-05, - "skip_indices": [ - 19876 - ], - "skip_examples": [ - "idx=19876: JPEGRuntimeError: tjDecompress2() failed with error -1 and error string Unsupported color conversion request" - ], - "num_runs": 3 -} \ No newline at end of file diff --git a/output/linux_AMD-EPYC-9B14/kornia_1t_results.json b/output/linux_AMD-EPYC-9B14/kornia_1t_results.json deleted file mode 100644 index 4387f07..0000000 --- a/output/linux_AMD-EPYC-9B14/kornia_1t_results.json +++ /dev/null @@ -1,57 +0,0 @@ -{ - "library": "kornia", - "mode": "memory", - "run_tag": "1t", - "requested_threads": 1, - "effective_threads": 1, - "system_info": { - "Python": "3.12.3", - "OS": "Linux", - "OS Version": "#12~24.04.1-Ubuntu SMP Fri Mar 27 23:35:04 UTC 2026", - "Machine": "x86_64", - "CPU": { - "brand_raw": "AMD EPYC 9B14", - "arch": "X86_64", - "hz_advertised_raw": "Unknown", - "count": 16 - }, - "kornia": "0.1.10" - }, - "benchmark_results": { - "images_per_second_mean": 641.6364831248035, - "images_per_second_std": 1.3165009625462982, - "images_per_second_p50": 641.8985498640346, - "images_per_second_p90": 643.05487145542, - "images_per_second_p99": 643.2744681565547, - "us_per_image_mean": 1558.521433508669, - "us_per_image_p50": 1557.8785778715614, - "us_per_image_p90": 1562.3267185743728, - "us_per_image_p99": 1563.0018910178203, - "raw_times_s": [ - 77.72281672400004, - 78.15228243199999, - 78.05851035700016, - 77.89237101500021, - 77.79658524199931 - ], - "raw_throughput_ips": [ - 643.2988677900141, - 639.7637848069753, - 640.5323362094646, - 641.8985498640346, - 642.6888769535287 - ], - "num_images_total": 50000, - "num_images_decoded": 49999, - "num_images_skipped": 1, - "skip_rate": 2e-05, - "skip_indices": [ - 19876 - ], - "skip_examples": [ - "idx=19876: ValueError: Unsupported number of channels: 4" - ] - }, - "num_images": 50000, - "num_runs": 5 -} \ No newline at end of file diff --git a/output/linux_AMD-EPYC-9B14/kornia_dataloader_results.json b/output/linux_AMD-EPYC-9B14/kornia_dataloader_results.json deleted file mode 100644 index 9191f13..0000000 --- a/output/linux_AMD-EPYC-9B14/kornia_dataloader_results.json +++ /dev/null @@ -1,125 +0,0 @@ -{ - "library": "kornia", - "benchmark_type": "dataloader", - "batch_size": 32, - "num_threads": 1, - "system_info": { - "Python": "3.12.3", - "OS": "Linux", - "OS Version": "#12~24.04.1-Ubuntu SMP Fri Mar 27 23:35:04 UTC 2026", - "Machine": "x86_64", - "CPU": { - "brand_raw": "AMD EPYC 9B14", - "arch": "X86_64", - "hz_advertised_raw": "Unknown", - "count": 16 - }, - "kornia": "0.1.10" - }, - "worker_results": [ - { - "num_workers": 0, - "num_warmup": 1, - "images_per_second_mean": 627.4049082712413, - "images_per_second_std": 0.9131717279643388, - "images_per_second_p50": 627.5014935737263, - "images_per_second_p90": 628.2778072802299, - "images_per_second_p99": 628.4524778641932, - "us_per_image_mean": 1593.8704108215436, - "us_per_image_p50": 1593.6217048740907, - "us_per_image_p90": 1596.187104662085, - "us_per_image_p99": 1596.7643196143838, - "raw_times_s": [ - 79.83982590199957, - 79.67949162199966, - 79.55646248799985 - ], - "raw_throughput_ips": [ - 626.2413455331418, - 627.5014935737263, - 628.4718857068558 - ] - }, - { - "num_workers": 2, - "num_warmup": 1, - "images_per_second_mean": 954.5674578077734, - "images_per_second_std": 2.028611859322228, - "images_per_second_p50": 955.3080114916445, - "images_per_second_p90": 956.3396111019538, - "images_per_second_p99": 956.5717210142733, - "us_per_image_mean": 1047.5996357927118, - "us_per_image_p50": 1046.7828050960989, - "us_per_image_p90": 1049.8720546570908, - "us_per_image_p99": 1050.5671358083139, - "raw_times_s": [ - 52.338093471999855, - 52.531167707999884, - 52.26754138999968 - ], - "raw_throughput_ips": [ - 955.3080114916445, - 951.7968509271446, - 956.597511004531 - ] - }, - { - "num_workers": 4, - "num_warmup": 1, - "images_per_second_mean": 1191.9562647717719, - "images_per_second_std": 6.214072150196748, - "images_per_second_p50": 1190.6329558333007, - "images_per_second_p90": 1198.2400257144852, - "images_per_second_p99": 1199.9516164377517, - "us_per_image_mean": 838.9797223877862, - "us_per_image_p50": 839.8894009280297, - "us_per_image_p90": 843.0297911918346, - "us_per_image_p99": 843.7363790011907, - "raw_times_s": [ - 41.99363015700055, - 42.18990062300054, - 41.66091063899967 - ], - "raw_throughput_ips": [ - 1190.6329558333007, - 1185.0940452972338, - 1200.1417931847814 - ] - }, - { - "num_workers": 8, - "num_warmup": 1, - "images_per_second_mean": 1326.6050545080823, - "images_per_second_std": 13.977118875488655, - "images_per_second_p50": 1335.6328108962243, - "images_per_second_p90": 1336.9824943649583, - "images_per_second_p99": 1337.2861731454234, - "us_per_image_mean": 753.8881604032105, - "us_per_image_p50": 748.7087707354157, - "us_per_image_p90": 761.8949365387356, - "us_per_image_p99": 764.8618238444825, - "raw_times_s": [ - 37.434689828000046, - 37.387463860000025, - 38.25880870800029 - ], - "raw_throughput_ips": [ - 1335.6328108962243, - 1337.3199152321417, - 1306.8624373958808 - ] - } - ], - "num_images": 49999, - "num_images_total": 50000, - "num_images_decoded": 49999, - "num_images_skipped": 1, - "skip_rate": 2e-05, - "skip_indices": [ - 19876 - ], - "skip_examples": [ - "idx=19876: ValueError: Unsupported number of channels: 4" - ], - "num_runs": 3 -} \ No newline at end of file diff --git a/output/linux_AMD-EPYC-9B14/opencv_1t_results.json b/output/linux_AMD-EPYC-9B14/opencv_1t_results.json deleted file mode 100644 index e5473a4..0000000 --- a/output/linux_AMD-EPYC-9B14/opencv_1t_results.json +++ /dev/null @@ -1,53 +0,0 @@ -{ - "library": "opencv", - "mode": "memory", - "run_tag": "1t", - "requested_threads": 1, - "effective_threads": 1, - "system_info": { - "Python": "3.12.3", - "OS": "Linux", - "OS Version": "#12~24.04.1-Ubuntu SMP Fri Mar 27 23:35:04 UTC 2026", - "Machine": "x86_64", - "CPU": { - "brand_raw": "AMD EPYC 9B14", - "arch": "X86_64", - "hz_advertised_raw": "Unknown", - "count": 16 - }, - "opencv": "4.13.0.92" - }, - "benchmark_results": { - "images_per_second_mean": 663.9024035606968, - "images_per_second_std": 0.62230402437606, - "images_per_second_p50": 664.0641189971427, - "images_per_second_p90": 664.4585063056246, - "images_per_second_p99": 664.5622552319165, - "us_per_image_mean": 1506.2468128199944, - "us_per_image_p50": 1505.8786815799976, - "us_per_image_p90": 1507.8707720239872, - "us_per_image_p99": 1508.7466558543863, - "raw_times_s": [ - 75.23619090500051, - 75.32054828199944, - 75.29393407899988, - 75.44219881399931, - 75.26883112499945 - ], - "raw_throughput_ips": [ - 664.5737828903934, - 663.8294746979332, - 664.0641189971427, - 662.7590497895434, - 664.2855914284714 - ], - "num_images_total": 50000, - "num_images_decoded": 50000, - "num_images_skipped": 0, - "skip_rate": 0.0, - "skip_indices": [], - "skip_examples": [] - }, - "num_images": 50000, - "num_runs": 5 -} \ No newline at end of file diff --git a/output/linux_AMD-EPYC-9B14/opencv_dataloader_results.json b/output/linux_AMD-EPYC-9B14/opencv_dataloader_results.json deleted file mode 100644 index 10a8312..0000000 --- a/output/linux_AMD-EPYC-9B14/opencv_dataloader_results.json +++ /dev/null @@ -1,121 +0,0 @@ -{ - "library": "opencv", - "benchmark_type": "dataloader", - "batch_size": 32, - "num_threads": 16, - "system_info": { - "Python": "3.12.3", - "OS": "Linux", - "OS Version": "#12~24.04.1-Ubuntu SMP Fri Mar 27 23:35:04 UTC 2026", - "Machine": "x86_64", - "CPU": { - "brand_raw": "AMD EPYC 9B14", - "arch": "X86_64", - "hz_advertised_raw": "Unknown", - "count": 16 - }, - "opencv": "4.13.0.92" - }, - "worker_results": [ - { - "num_workers": 0, - "num_warmup": 1, - "images_per_second_mean": 653.6120994486208, - "images_per_second_std": 0.623529420345201, - "images_per_second_p50": 653.3975495802644, - "images_per_second_p90": 654.2475819414228, - "images_per_second_p99": 654.4388392226834, - "us_per_image_mean": 1529.9608292133355, - "us_per_image_p50": 1530.4618155400021, - "us_per_image_p90": 1531.247257988005, - "us_per_image_p99": 1531.4239825388056, - "raw_times_s": [ - 76.57218093000029, - 76.39885267499994, - 76.5230907770001 - ], - "raw_throughput_ips": [ - 652.9786587338856, - 654.4600900317124, - 653.3975495802644 - ] - }, - { - "num_workers": 2, - "num_warmup": 1, - "images_per_second_mean": 1060.4779455576383, - "images_per_second_std": 1.529984812154355, - "images_per_second_p50": 1059.593162821302, - "images_per_second_p90": 1062.0229137821063, - "images_per_second_p99": 1062.5696077482874, - "us_per_image_mean": 942.9730091466687, - "us_per_image_p50": 943.7584490799964, - "us_per_image_p90": 944.0313383759967, - "us_per_image_p99": 944.0927384675967, - "raw_times_s": [ - 47.20497803499984, - 47.18792245399982, - 47.05305088300065 - ], - "raw_throughput_ips": [ - 1059.210322329306, - 1059.593162821302, - 1062.6303515223074 - ] - }, - { - "num_workers": 4, - "num_warmup": 1, - "images_per_second_mean": 1457.4244702782253, - "images_per_second_std": 2.983938674004969, - "images_per_second_p50": 1458.642929353067, - "images_per_second_p90": 1459.9799043985486, - "images_per_second_p99": 1460.280723783782, - "us_per_image_mean": 686.1447827733355, - "us_per_image_p50": 685.5687432999912, - "us_per_image_p90": 687.5789031880049, - "us_per_image_p99": 688.031189162808, - "raw_times_s": [ - 34.23920809300034, - 34.40407215800042, - 34.27843716499956 - ], - "raw_throughput_ips": [ - 1460.314148159919, - 1453.3163333216896, - 1458.642929353067 - ] - }, - { - "num_workers": 8, - "num_warmup": 1, - "images_per_second_mean": 1402.0686063786668, - "images_per_second_std": 81.61673550631919, - "images_per_second_p50": 1346.9245433953042, - "images_per_second_p90": 1483.3485639240862, - "images_per_second_p99": 1514.0439685430622, - "us_per_image_mean": 715.561039093324, - "us_per_image_p50": 742.4320871599957, - "us_per_image_p90": 744.6885916879874, - "us_per_image_p99": 745.1963052067855, - "raw_times_s": [ - 37.26263589099926, - 37.121604357999786, - 32.949915614999554 - ], - "raw_throughput_ips": [ - 1341.8267066844144, - 1346.9245433953042, - 1517.4545690562818 - ] - } - ], - "num_images": 50000, - "num_images_total": 50000, - "num_images_decoded": 50000, - "num_images_skipped": 0, - "skip_rate": 0.0, - "skip_indices": [], - "skip_examples": [], - "num_runs": 3 -} \ No newline at end of file diff --git a/output/linux_AMD-EPYC-9B14/opencv_default_results.json b/output/linux_AMD-EPYC-9B14/opencv_default_results.json deleted file mode 100644 index 9d080bf..0000000 --- a/output/linux_AMD-EPYC-9B14/opencv_default_results.json +++ /dev/null @@ -1,53 +0,0 @@ -{ - "library": "opencv", - "mode": "memory", - "run_tag": "default", - "requested_threads": 0, - "effective_threads": 16, - "system_info": { - "Python": "3.12.3", - "OS": "Linux", - "OS Version": "#12~24.04.1-Ubuntu SMP Fri Mar 27 23:35:04 UTC 2026", - "Machine": "x86_64", - "CPU": { - "brand_raw": "AMD EPYC 9B14", - "arch": "X86_64", - "hz_advertised_raw": "Unknown", - "count": 16 - }, - "opencv": "4.13.0.92" - }, - "benchmark_results": { - "images_per_second_mean": 661.7708982335314, - "images_per_second_std": 0.22639938903664855, - "images_per_second_p50": 661.8378042424407, - "images_per_second_p90": 661.9677836489432, - "images_per_second_p99": 662.0087582203327, - "us_per_image_mean": 1511.0971481960041, - "us_per_image_p50": 1510.9442125999885, - "us_per_image_p90": 1511.6889965280025, - "us_per_image_p99": 1512.019303648801, - "raw_times_s": [ - 75.52718226800062, - 75.54721062999943, - 75.60280022200004, - 75.55692423300025, - 75.5401696960007 - ], - "raw_throughput_ips": [ - 662.013310950487, - 661.8378042424407, - 661.3511649460074, - 661.7527183320943, - 661.8994926966272 - ], - "num_images_total": 50000, - "num_images_decoded": 50000, - "num_images_skipped": 0, - "skip_rate": 0.0, - "skip_indices": [], - "skip_examples": [] - }, - "num_images": 50000, - "num_runs": 5 -} \ No newline at end of file diff --git a/output/linux_AMD-EPYC-9B14/pillow_1t_results.json b/output/linux_AMD-EPYC-9B14/pillow_1t_results.json deleted file mode 100644 index 7862a0c..0000000 --- a/output/linux_AMD-EPYC-9B14/pillow_1t_results.json +++ /dev/null @@ -1,53 +0,0 @@ -{ - "library": "pillow", - "mode": "memory", - "run_tag": "1t", - "requested_threads": 1, - "effective_threads": 1, - "system_info": { - "Python": "3.12.3", - "OS": "Linux", - "OS Version": "#12~24.04.1-Ubuntu SMP Fri Mar 27 23:35:04 UTC 2026", - "Machine": "x86_64", - "CPU": { - "brand_raw": "AMD EPYC 9B14", - "arch": "X86_64", - "hz_advertised_raw": "Unknown", - "count": 16 - }, - "pillow": "12.2.0" - }, - "benchmark_results": { - "images_per_second_mean": 536.5520939199869, - "images_per_second_std": 4.451955022529096, - "images_per_second_p50": 535.6110071090977, - "images_per_second_p90": 541.7316540362153, - "images_per_second_p99": 543.871806898757, - "us_per_image_mean": 1863.8796843799937, - "us_per_image_p50": 1867.026604620005, - "us_per_image_p90": 1879.2442781919817, - "us_per_image_p99": 1883.1122098911728, - "raw_times_s": [ - 93.35133023100025, - 94.17709900399859, - 92.90835495400097, - 91.89325063799879, - 93.63988626799983 - ], - "raw_throughput_ips": [ - 535.6110071090977, - 530.9146334808751, - 538.1647325986457, - 544.1096016612616, - 533.9604947500543 - ], - "num_images_total": 50000, - "num_images_decoded": 50000, - "num_images_skipped": 0, - "skip_rate": 0.0, - "skip_indices": [], - "skip_examples": [] - }, - "num_images": 50000, - "num_runs": 5 -} \ No newline at end of file diff --git a/output/linux_AMD-EPYC-9B14/pillow_dataloader_results.json b/output/linux_AMD-EPYC-9B14/pillow_dataloader_results.json deleted file mode 100644 index 420ee52..0000000 --- a/output/linux_AMD-EPYC-9B14/pillow_dataloader_results.json +++ /dev/null @@ -1,121 +0,0 @@ -{ - "library": "pillow", - "benchmark_type": "dataloader", - "batch_size": 32, - "num_threads": 1, - "system_info": { - "Python": "3.12.3", - "OS": "Linux", - "OS Version": "#12~24.04.1-Ubuntu SMP Fri Mar 27 23:35:04 UTC 2026", - "Machine": "x86_64", - "CPU": { - "brand_raw": "AMD EPYC 9B14", - "arch": "X86_64", - "hz_advertised_raw": "Unknown", - "count": 16 - }, - "pillow": "12.2.0" - }, - "worker_results": [ - { - "num_workers": 0, - "num_warmup": 1, - "images_per_second_mean": 517.7122267589708, - "images_per_second_std": 1.4690084199297655, - "images_per_second_p50": 516.9572654780768, - "images_per_second_p90": 519.2041449531491, - "images_per_second_p99": 519.7096928350404, - "us_per_image_mean": 1931.5905348666638, - "us_per_image_p50": 1934.3958713399843, - "us_per_image_p90": 1936.0252027479946, - "us_per_image_p99": 1936.391802314797, - "raw_times_s": [ - 96.82162677999986, - 96.71979356699921, - 96.1971598830005 - ], - "raw_throughput_ips": [ - 516.4135499769184, - 516.9572654780768, - 519.7658648219173 - ] - }, - { - "num_workers": 2, - "num_warmup": 1, - "images_per_second_mean": 844.4682070389472, - "images_per_second_std": 0.4537296928855843, - "images_per_second_p50": 844.5220168860346, - "images_per_second_p90": 844.9004416485334, - "images_per_second_p99": 844.9855872200957, - "us_per_image_mean": 1184.1775455600025, - "us_per_image_p50": 1184.1017522399852, - "us_per_image_p90": 1184.8139473919946, - "us_per_image_p99": 1184.9741913011967, - "raw_times_s": [ - 59.24959980899985, - 59.20508761199926, - 59.171944413001256 - ], - "raw_throughput_ips": [ - 843.8875563916491, - 844.5220168860346, - 844.9950478391581 - ] - }, - { - "num_workers": 4, - "num_warmup": 1, - "images_per_second_mean": 1283.1105938718172, - "images_per_second_std": 11.083415829196102, - "images_per_second_p50": 1277.8380144711332, - "images_per_second_p90": 1294.3917600973812, - "images_per_second_p99": 1298.116352863287, - "us_per_image_mean": 779.4138995666799, - "us_per_image_p50": 782.5718038400009, - "us_per_image_p90": 784.9691087680185, - "us_per_image_p99": 785.5085023768224, - "raw_times_s": [ - 39.12859019200005, - 39.278421750001144, - 38.505072993000795 - ], - "raw_throughput_ips": [ - 1277.8380144711332, - 1272.9635706403744, - 1298.5301965039432 - ] - }, - { - "num_workers": 8, - "num_warmup": 1, - "images_per_second_mean": 1275.6679697109141, - "images_per_second_std": 20.604647435605532, - "images_per_second_p50": 1278.947501625216, - "images_per_second_p90": 1295.072145030551, - "images_per_second_p99": 1298.7001897967511, - "us_per_image_mean": 784.1084076533298, - "us_per_image_p50": 781.892922679981, - "us_per_image_p90": 796.9150458000076, - "us_per_image_p99": 800.2950235020137, - "raw_times_s": [ - 38.4880861849997, - 39.09464613399905, - 40.033528829000716 - ], - "raw_throughput_ips": [ - 1299.1033058818846, - 1278.947501625216, - 1248.953101625642 - ] - } - ], - "num_images": 50000, - "num_images_total": 50000, - "num_images_decoded": 50000, - "num_images_skipped": 0, - "skip_rate": 0.0, - "skip_indices": [], - "skip_examples": [], - "num_runs": 3 -} \ No newline at end of file diff --git a/output/linux_AMD-EPYC-9B14/pyvips_1t_results.json b/output/linux_AMD-EPYC-9B14/pyvips_1t_results.json deleted file mode 100644 index 5eddbd0..0000000 --- a/output/linux_AMD-EPYC-9B14/pyvips_1t_results.json +++ /dev/null @@ -1,53 +0,0 @@ -{ - "library": "pyvips", - "mode": "memory", - "run_tag": "1t", - "requested_threads": 1, - "effective_threads": 1, - "system_info": { - "Python": "3.12.3", - "OS": "Linux", - "OS Version": "#12~24.04.1-Ubuntu SMP Fri Mar 27 23:35:04 UTC 2026", - "Machine": "x86_64", - "CPU": { - "brand_raw": "AMD EPYC 9B14", - "arch": "X86_64", - "hz_advertised_raw": "Unknown", - "count": 16 - }, - "pyvips": "3.1.1" - }, - "benchmark_results": { - "images_per_second_mean": 419.72609168352847, - "images_per_second_std": 2.7835264890267815, - "images_per_second_p50": 418.3945773086937, - "images_per_second_p90": 423.16476218463356, - "images_per_second_p99": 424.136896081897, - "us_per_image_mean": 2382.6105725279995, - "us_per_image_p50": 2390.088338220012, - "us_per_image_p90": 2397.4889313999884, - "us_per_image_p99": 2399.956644063974, - "raw_times_s": [ - 117.85645203599961, - 120.01154171799863, - 119.50441691100059, - 119.6688038480006, - 118.61142861900043 - ], - "raw_throughput_ips": [ - 424.2449109593707, - 416.62659511107086, - 418.3945773086937, - 417.81983601597926, - 421.54453902252783 - ], - "num_images_total": 50000, - "num_images_decoded": 50000, - "num_images_skipped": 0, - "skip_rate": 0.0, - "skip_indices": [], - "skip_examples": [] - }, - "num_images": 50000, - "num_runs": 5 -} \ No newline at end of file diff --git a/output/linux_AMD-EPYC-9B14/pyvips_default_results.json b/output/linux_AMD-EPYC-9B14/pyvips_default_results.json deleted file mode 100644 index c40a221..0000000 --- a/output/linux_AMD-EPYC-9B14/pyvips_default_results.json +++ /dev/null @@ -1,53 +0,0 @@ -{ - "library": "pyvips", - "mode": "memory", - "run_tag": "default", - "requested_threads": 0, - "effective_threads": 16, - "system_info": { - "Python": "3.12.3", - "OS": "Linux", - "OS Version": "#12~24.04.1-Ubuntu SMP Fri Mar 27 23:35:04 UTC 2026", - "Machine": "x86_64", - "CPU": { - "brand_raw": "AMD EPYC 9B14", - "arch": "X86_64", - "hz_advertised_raw": "Unknown", - "count": 16 - }, - "pyvips": "3.1.1" - }, - "benchmark_results": { - "images_per_second_mean": 373.0045096236681, - "images_per_second_std": 0.9827895249959594, - "images_per_second_p50": 373.02958373616025, - "images_per_second_p90": 374.04364822104924, - "images_per_second_p99": 374.54590772681854, - "us_per_image_mean": 2680.9513359760167, - "us_per_image_p50": 2680.7525290200283, - "us_per_image_p90": 2688.2834806320025, - "us_per_image_p99": 2691.066504739187, - "raw_times_s": [ - 133.47509657900082, - 134.03762645100142, - 133.97406912600127, - 134.1822553560014, - 134.56878648199927 - ], - "raw_throughput_ips": [ - 374.6017143385707, - 373.02958373616025, - 373.20654904476703, - 372.62751223955865, - 371.5571887592841 - ], - "num_images_total": 50000, - "num_images_decoded": 50000, - "num_images_skipped": 0, - "skip_rate": 0.0, - "skip_indices": [], - "skip_examples": [] - }, - "num_images": 50000, - "num_runs": 5 -} \ No newline at end of file diff --git a/output/linux_AMD-EPYC-9B14/run_summary.json b/output/linux_AMD-EPYC-9B14/run_summary.json deleted file mode 100644 index c0c276f..0000000 --- a/output/linux_AMD-EPYC-9B14/run_summary.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "timestamp_utc": "2026-05-20T07:26:36.975587+00:00", - "system": "linux_AMD-EPYC-9B14", - "mode": "both", - "num_images": 50000, - "num_runs": 5, - "dataloader_runs": 3, - "workers": [ - 0, - 2, - 4, - 8 - ], - "libs_requested": [ - "ajpegli" - ], - "libs_skipped_platform": [], - "libs_run": [ - "ajpegli" - ], - "failures": [], - "exit_status": "ok" -} \ No newline at end of file diff --git a/output/linux_AMD-EPYC-9B14/simplejpeg_1t_results.json b/output/linux_AMD-EPYC-9B14/simplejpeg_1t_results.json deleted file mode 100644 index 2b24b53..0000000 --- a/output/linux_AMD-EPYC-9B14/simplejpeg_1t_results.json +++ /dev/null @@ -1,53 +0,0 @@ -{ - "library": "simplejpeg", - "mode": "memory", - "run_tag": "1t", - "requested_threads": 1, - "effective_threads": 1, - "system_info": { - "Python": "3.12.3", - "OS": "Linux", - "OS Version": "#12~24.04.1-Ubuntu SMP Fri Mar 27 23:35:04 UTC 2026", - "Machine": "x86_64", - "CPU": { - "brand_raw": "AMD EPYC 9B14", - "arch": "X86_64", - "hz_advertised_raw": "Unknown", - "count": 16 - }, - "simplejpeg": "1.9.0" - }, - "benchmark_results": { - "images_per_second_mean": 690.2538178990242, - "images_per_second_std": 0.27578976296628765, - "images_per_second_p50": 690.3906618409823, - "images_per_second_p90": 690.454029003665, - "images_per_second_p99": 690.4821356155155, - "us_per_image_mean": 1448.7426708679995, - "us_per_image_p50": 1448.4552808599983, - "us_per_image_p90": 1449.4047488519936, - "us_per_image_p99": 1449.8167376112033, - "raw_times_s": [ - 72.49312570700022, - 72.43590504599888, - 72.42276404299992, - 72.42103082299946, - 72.41284209800142 - ], - "raw_throughput_ips": [ - 689.720570224658, - 690.2654142065121, - 690.3906618409823, - 690.4071846505809, - 690.4852585723878 - ], - "num_images_total": 50000, - "num_images_decoded": 50000, - "num_images_skipped": 0, - "skip_rate": 0.0, - "skip_indices": [], - "skip_examples": [] - }, - "num_images": 50000, - "num_runs": 5 -} \ No newline at end of file diff --git a/output/linux_AMD-EPYC-9B14/simplejpeg_dataloader_results.json b/output/linux_AMD-EPYC-9B14/simplejpeg_dataloader_results.json deleted file mode 100644 index f4689fa..0000000 --- a/output/linux_AMD-EPYC-9B14/simplejpeg_dataloader_results.json +++ /dev/null @@ -1,121 +0,0 @@ -{ - "library": "simplejpeg", - "benchmark_type": "dataloader", - "batch_size": 32, - "num_threads": 1, - "system_info": { - "Python": "3.12.3", - "OS": "Linux", - "OS Version": "#12~24.04.1-Ubuntu SMP Fri Mar 27 23:35:04 UTC 2026", - "Machine": "x86_64", - "CPU": { - "brand_raw": "AMD EPYC 9B14", - "arch": "X86_64", - "hz_advertised_raw": "Unknown", - "count": 16 - }, - "simplejpeg": "1.9.0" - }, - "worker_results": [ - { - "num_workers": 0, - "num_warmup": 1, - "images_per_second_mean": 683.7539404529007, - "images_per_second_std": 0.4181510293831867, - "images_per_second_p50": 683.8779845045982, - "images_per_second_p90": 684.1297193947374, - "images_per_second_p99": 684.1863597450188, - "us_per_image_mean": 1462.5149705599931, - "us_per_image_p50": 1462.2491477400035, - "us_per_image_p90": 1463.4251275959978, - "us_per_image_p99": 1463.6897230635964, - "raw_times_s": [ - 73.18595612799982, - 73.07883206899896, - 73.11245738700018 - ], - "raw_throughput_ips": [ - 683.1911837368314, - 684.1926531172722, - 683.8779845045982 - ] - }, - { - "num_workers": 2, - "num_warmup": 1, - "images_per_second_mean": 1110.0906408332323, - "images_per_second_std": 4.174839488513961, - "images_per_second_p50": 1111.5145760820178, - "images_per_second_p90": 1113.775598032183, - "images_per_second_p99": 1114.2843279709705, - "us_per_image_mean": 900.840104680004, - "us_per_image_p50": 899.6733120000135, - "us_per_image_p90": 904.299071247995, - "us_per_image_p99": 905.3398670787908, - "raw_times_s": [ - 44.98366560000068, - 45.27277555299952, - 44.86957454900039 - ], - "raw_throughput_ips": [ - 1111.5145760820178, - 1104.4164928979549, - 1114.3408535197245 - ] - }, - { - "num_workers": 4, - "num_warmup": 1, - "images_per_second_mean": 1521.1686425884457, - "images_per_second_std": 4.686323366399505, - "images_per_second_p50": 1519.4358119070514, - "images_per_second_p90": 1525.9471230498104, - "images_per_second_p99": 1527.4121680569313, - "us_per_image_mean": 657.3955367933377, - "us_per_image_p50": 658.1390224999996, - "us_per_image_p90": 659.159984260019, - "us_per_image_p99": 659.3897006560234, - "raw_times_s": [ - 32.97076123500119, - 32.73161815899948, - 32.90695112499998 - ], - "raw_throughput_ips": [ - 1516.4951650227858, - 1527.5749508355002, - 1519.4358119070514 - ] - }, - { - "num_workers": 8, - "num_warmup": 1, - "images_per_second_mean": 1510.8045263614652, - "images_per_second_std": 73.35231308393882, - "images_per_second_p50": 1464.4774477214194, - "images_per_second_p90": 1584.3751846286293, - "images_per_second_p99": 1611.3521754327514, - "us_per_image_mean": 663.4118240733491, - "us_per_image_p50": 682.8374185999928, - "us_per_image_p90": 686.9303228880235, - "us_per_image_p99": 687.8512263528304, - "raw_times_s": [ - 34.39767744800156, - 34.14187092999964, - 30.972225233001154 - ], - "raw_throughput_ips": [ - 1453.5865125075445, - 1464.4774477214194, - 1614.3496188554318 - ] - } - ], - "num_images": 50000, - "num_images_total": 50000, - "num_images_decoded": 50000, - "num_images_skipped": 0, - "skip_rate": 0.0, - "skip_indices": [], - "skip_examples": [], - "num_runs": 3 -} \ No newline at end of file diff --git a/output/linux_AMD-EPYC-9B14/skimage_1t_results.json b/output/linux_AMD-EPYC-9B14/skimage_1t_results.json deleted file mode 100644 index 91122d9..0000000 --- a/output/linux_AMD-EPYC-9B14/skimage_1t_results.json +++ /dev/null @@ -1,53 +0,0 @@ -{ - "library": "skimage", - "mode": "memory", - "run_tag": "1t", - "requested_threads": 1, - "effective_threads": 1, - "system_info": { - "Python": "3.12.3", - "OS": "Linux", - "OS Version": "#12~24.04.1-Ubuntu SMP Fri Mar 27 23:35:04 UTC 2026", - "Machine": "x86_64", - "CPU": { - "brand_raw": "AMD EPYC 9B14", - "arch": "X86_64", - "hz_advertised_raw": "Unknown", - "count": 16 - }, - "skimage": "0.26.0" - }, - "benchmark_results": { - "images_per_second_mean": 475.1574183308314, - "images_per_second_std": 2.0030436713300666, - "images_per_second_p50": 475.5512218500812, - "images_per_second_p90": 477.276743274713, - "images_per_second_p99": 477.5488355524259, - "us_per_image_mean": 2104.603137356011, - "us_per_image_p50": 2102.8229012000156, - "us_per_image_p90": 2115.242758104003, - "us_per_image_p99": 2117.7627388943993, - "raw_times_s": [ - 105.90213683799993, - 104.69470575100058, - 105.14114506000078, - 104.86065718400096, - 105.55213950600046 - ], - "raw_throughput_ips": [ - 472.13400496805593, - 477.5790680277273, - 475.5512218500812, - 476.82325614519146, - 473.69954066310123 - ], - "num_images_total": 50000, - "num_images_decoded": 50000, - "num_images_skipped": 0, - "skip_rate": 0.0, - "skip_indices": [], - "skip_examples": [] - }, - "num_images": 50000, - "num_runs": 5 -} \ No newline at end of file diff --git a/output/linux_AMD-EPYC-9B14/skimage_dataloader_results.json b/output/linux_AMD-EPYC-9B14/skimage_dataloader_results.json deleted file mode 100644 index 71f1355..0000000 --- a/output/linux_AMD-EPYC-9B14/skimage_dataloader_results.json +++ /dev/null @@ -1,121 +0,0 @@ -{ - "library": "skimage", - "benchmark_type": "dataloader", - "batch_size": 32, - "num_threads": 1, - "system_info": { - "Python": "3.12.3", - "OS": "Linux", - "OS Version": "#12~24.04.1-Ubuntu SMP Fri Mar 27 23:35:04 UTC 2026", - "Machine": "x86_64", - "CPU": { - "brand_raw": "AMD EPYC 9B14", - "arch": "X86_64", - "hz_advertised_raw": "Unknown", - "count": 16 - }, - "skimage": "0.26.0" - }, - "worker_results": [ - { - "num_workers": 0, - "num_warmup": 1, - "images_per_second_mean": 474.9833032155807, - "images_per_second_std": 1.505379988309336, - "images_per_second_p50": 474.84979452435806, - "images_per_second_p90": 476.48206689509357, - "images_per_second_p99": 476.8493281785091, - "us_per_image_mean": 2105.3583017266646, - "us_per_image_p50": 2105.9290991200032, - "us_per_image_p90": 2111.767239456014, - "us_per_image_p99": 2113.0808210316163, - "raw_times_s": [ - 105.66133872700084, - 104.8459515759987, - 105.29645495600016 - ], - "raw_throughput_ips": [ - 473.20998013460655, - 476.89013498777746, - 474.84979452435806 - ] - }, - { - "num_workers": 2, - "num_warmup": 1, - "images_per_second_mean": 821.185268549889, - "images_per_second_std": 1.973758579670832, - "images_per_second_p50": 821.535254831352, - "images_per_second_p90": 823.0338902299486, - "images_per_second_p99": 823.3710831946329, - "us_per_image_mean": 1217.7590356399985, - "us_per_image_p50": 1217.233215640008, - "us_per_image_p90": 1220.7105951599951, - "us_per_image_p99": 1221.4930055519922, - "raw_times_s": [ - 60.72319756199977, - 60.8616607820004, - 61.078997001999596 - ], - "raw_throughput_ips": [ - 823.4085490795978, - 821.535254831352, - 818.6120017387173 - ] - }, - { - "num_workers": 4, - "num_warmup": 1, - "images_per_second_mean": 1237.9356844664255, - "images_per_second_std": 13.724479634113365, - "images_per_second_p50": 1230.4547881011297, - "images_per_second_p90": 1251.8400972387594, - "images_per_second_p99": 1256.651791794726, - "us_per_image_mean": 807.8949931333287, - "us_per_image_p50": 812.707634340004, - "us_per_image_p90": 814.9818195239859, - "us_per_image_p99": 815.4935111903818, - "raw_times_s": [ - 40.77751829099907, - 40.6353817170002, - 39.77134896200005 - ], - "raw_throughput_ips": [ - 1226.16584077498, - 1230.4547881011297, - 1257.1864245231668 - ] - }, - { - "num_workers": 8, - "num_warmup": 1, - "images_per_second_mean": 1211.9052687859928, - "images_per_second_std": 17.001848582400523, - "images_per_second_p50": 1218.6706787934527, - "images_per_second_p90": 1226.5375041156326, - "images_per_second_p99": 1228.3075398131232, - "us_per_image_mean": 825.3106676066697, - "us_per_image_p50": 820.5662262999977, - "us_per_image_p90": 837.2074471320084, - "us_per_image_p99": 840.9517218192108, - "raw_times_s": [ - 42.06838761700055, - 40.69990120900002, - 41.02831131499988 - ], - "raw_throughput_ips": [ - 1188.5409171183483, - 1228.5042104461777, - 1218.6706787934527 - ] - } - ], - "num_images": 50000, - "num_images_total": 50000, - "num_images_decoded": 50000, - "num_images_skipped": 0, - "skip_rate": 0.0, - "skip_indices": [], - "skip_examples": [], - "num_runs": 3 -} \ No newline at end of file diff --git a/output/linux_AMD-EPYC-9B14/tensorflow_1t_results.json b/output/linux_AMD-EPYC-9B14/tensorflow_1t_results.json deleted file mode 100644 index cd5b0f9..0000000 --- a/output/linux_AMD-EPYC-9B14/tensorflow_1t_results.json +++ /dev/null @@ -1,53 +0,0 @@ -{ - "library": "tensorflow", - "mode": "memory", - "run_tag": "1t", - "requested_threads": 1, - "effective_threads": 1, - "system_info": { - "Python": "3.12.3", - "OS": "Linux", - "OS Version": "#12~24.04.1-Ubuntu SMP Fri Mar 27 23:35:04 UTC 2026", - "Machine": "x86_64", - "CPU": { - "brand_raw": "AMD EPYC 9B14", - "arch": "X86_64", - "hz_advertised_raw": "Unknown", - "count": 16 - }, - "tensorflow": "2.21.0" - }, - "benchmark_results": { - "images_per_second_mean": 596.1281442788618, - "images_per_second_std": 0.7889061851390908, - "images_per_second_p50": 596.327207229806, - "images_per_second_p90": 596.932012659425, - "images_per_second_p99": 597.2396894902008, - "us_per_image_mean": 1677.4946147240262, - "us_per_image_p50": 1676.931704400049, - "us_per_image_p90": 1680.0288665120315, - "us_per_image_p99": 1680.8625580832188, - "raw_times_s": [ - 84.04775952400087, - 83.71368985899971, - 83.84658522000245, - 83.93196902800264, - 83.83365005000087 - ], - "raw_throughput_ips": [ - 594.8998555484621, - 597.2738758047315, - 596.327207229806, - 595.7205648698442, - 596.4192179414653 - ], - "num_images_total": 50000, - "num_images_decoded": 50000, - "num_images_skipped": 0, - "skip_rate": 0.0, - "skip_indices": [], - "skip_examples": [] - }, - "num_images": 50000, - "num_runs": 5 -} \ No newline at end of file diff --git a/output/linux_AMD-EPYC-9B14/torchvision_1t_results.json b/output/linux_AMD-EPYC-9B14/torchvision_1t_results.json deleted file mode 100644 index 4cfded1..0000000 --- a/output/linux_AMD-EPYC-9B14/torchvision_1t_results.json +++ /dev/null @@ -1,53 +0,0 @@ -{ - "library": "torchvision", - "mode": "memory", - "run_tag": "1t", - "requested_threads": 1, - "effective_threads": 1, - "system_info": { - "Python": "3.12.3", - "OS": "Linux", - "OS Version": "#12~24.04.1-Ubuntu SMP Fri Mar 27 23:35:04 UTC 2026", - "Machine": "x86_64", - "CPU": { - "brand_raw": "AMD EPYC 9B14", - "arch": "X86_64", - "hz_advertised_raw": "Unknown", - "count": 16 - }, - "torchvision": "0.26.0+cpu" - }, - "benchmark_results": { - "images_per_second_mean": 620.7428895602865, - "images_per_second_std": 0.8723138528302674, - "images_per_second_p50": 621.0073331628357, - "images_per_second_p90": 621.3736839086904, - "images_per_second_p99": 621.429314955187, - "us_per_image_mean": 1610.9761309640016, - "us_per_image_p50": 1610.2869428399936, - "us_per_image_p90": 1613.4193713400018, - "us_per_image_p99": 1615.2197702200006, - "raw_times_s": [ - 80.77099072700003, - 80.47888600599981, - 80.4588735390007, - 80.51434714199968, - 80.52093532700019 - ], - "raw_throughput_ips": [ - 619.0341303228074, - 621.2809654978628, - 621.4354961825755, - 621.0073331628357, - 620.9565226353507 - ], - "num_images_total": 50000, - "num_images_decoded": 50000, - "num_images_skipped": 0, - "skip_rate": 0.0, - "skip_indices": [], - "skip_examples": [] - }, - "num_images": 50000, - "num_runs": 5 -} \ No newline at end of file diff --git a/output/linux_AMD-EPYC-9B14/torchvision_dataloader_results.json b/output/linux_AMD-EPYC-9B14/torchvision_dataloader_results.json deleted file mode 100644 index 6328316..0000000 --- a/output/linux_AMD-EPYC-9B14/torchvision_dataloader_results.json +++ /dev/null @@ -1,121 +0,0 @@ -{ - "library": "torchvision", - "benchmark_type": "dataloader", - "batch_size": 32, - "num_threads": 8, - "system_info": { - "Python": "3.12.3", - "OS": "Linux", - "OS Version": "#12~24.04.1-Ubuntu SMP Fri Mar 27 23:35:04 UTC 2026", - "Machine": "x86_64", - "CPU": { - "brand_raw": "AMD EPYC 9B14", - "arch": "X86_64", - "hz_advertised_raw": "Unknown", - "count": 16 - }, - "torchvision": "0.26.0+cpu" - }, - "worker_results": [ - { - "num_workers": 0, - "num_warmup": 1, - "images_per_second_mean": 612.5193990803277, - "images_per_second_std": 1.4705924201186131, - "images_per_second_p50": 613.2553934886853, - "images_per_second_p90": 613.7198374845316, - "images_per_second_p99": 613.824337383597, - "us_per_image_mean": 1632.6107785066415, - "us_per_image_p50": 1630.6419977999758, - "us_per_image_p90": 1636.600856983976, - "us_per_image_p99": 1637.941600300376, - "raw_times_s": [ - 81.45498829699864, - 81.53209988999879, - 81.9045285889988 - ], - "raw_throughput_ips": [ - 613.8359484834932, - 613.2553934886853, - 610.4668552688046 - ] - }, - { - "num_workers": 2, - "num_warmup": 1, - "images_per_second_mean": 1030.3472709857976, - "images_per_second_std": 4.238564800540255, - "images_per_second_p50": 1032.106570661855, - "images_per_second_p90": 1033.9654424531773, - "images_per_second_p99": 1034.383688606225, - "us_per_image_mean": 970.5630224733128, - "us_per_image_p50": 968.8921942999878, - "us_per_image_p90": 974.6432819319598, - "us_per_image_p99": 975.9372766491535, - "raw_times_s": [ - 48.3357909639999, - 48.44460971499939, - 48.80405269199764 - ], - "raw_throughput_ips": [ - 1034.430160401008, - 1032.106570661855, - 1024.5050818945301 - ] - }, - { - "num_workers": 4, - "num_warmup": 1, - "images_per_second_mean": 1495.4038394762872, - "images_per_second_std": 11.05970035204923, - "images_per_second_p50": 1489.6650326088607, - "images_per_second_p90": 1506.6320779692574, - "images_per_second_p99": 1510.4496631753466, - "us_per_image_mean": 668.7520905666801, - "us_per_image_p50": 671.2918529400486, - "us_per_image_p90": 672.7350006040069, - "us_per_image_p99": 673.0597088283976, - "raw_times_s": [ - 33.654789375999826, - 33.09343156199975, - 33.56459264700243 - ], - "raw_throughput_ips": [ - 1485.6726465106449, - 1510.8738393093565, - 1489.6650326088607 - ] - }, - { - "num_workers": 8, - "num_warmup": 1, - "images_per_second_mean": 1595.5044108040556, - "images_per_second_std": 70.72741042404212, - "images_per_second_p50": 1641.5149935958095, - "images_per_second_p90": 1647.8337864430955, - "images_per_second_p99": 1649.2555148337349, - "us_per_image_mean": 628.0347485466336, - "us_per_image_p50": 609.1933390199847, - "us_per_image_p90": 656.7465008599393, - "us_per_image_p99": 667.445962273929, - "raw_times_s": [ - 33.431739565996395, - 30.313805764999415, - 30.459666950999235 - ], - "raw_throughput_ips": [ - 1495.5847541614398, - 1649.413484654917, - 1641.5149935958095 - ] - } - ], - "num_images": 50000, - "num_images_total": 50000, - "num_images_decoded": 50000, - "num_images_skipped": 0, - "skip_rate": 0.0, - "skip_indices": [], - "skip_examples": [], - "num_runs": 3 -} \ No newline at end of file diff --git a/output/linux_AMD-EPYC-9B14/torchvision_default_results.json b/output/linux_AMD-EPYC-9B14/torchvision_default_results.json deleted file mode 100644 index b689444..0000000 --- a/output/linux_AMD-EPYC-9B14/torchvision_default_results.json +++ /dev/null @@ -1,53 +0,0 @@ -{ - "library": "torchvision", - "mode": "memory", - "run_tag": "default", - "requested_threads": 0, - "effective_threads": 8, - "system_info": { - "Python": "3.12.3", - "OS": "Linux", - "OS Version": "#12~24.04.1-Ubuntu SMP Fri Mar 27 23:35:04 UTC 2026", - "Machine": "x86_64", - "CPU": { - "brand_raw": "AMD EPYC 9B14", - "arch": "X86_64", - "hz_advertised_raw": "Unknown", - "count": 16 - }, - "torchvision": "0.26.0+cpu" - }, - "benchmark_results": { - "images_per_second_mean": 619.6419800385294, - "images_per_second_std": 1.7770479910624062, - "images_per_second_p50": 620.1967550588306, - "images_per_second_p90": 621.4450456751125, - "images_per_second_p99": 621.6327566681638, - "us_per_image_mean": 1613.8484258079989, - "us_per_image_p50": 1612.3915384000065, - "us_per_image_p90": 1619.5054303560028, - "us_per_image_p99": 1620.8309063135966, - "raw_times_s": [ - 80.61957692000033, - 80.43064323699946, - 80.49816203599948, - 80.86481518800065, - 81.0489090709998 - ], - "raw_throughput_ips": [ - 620.1967550588306, - 621.6536134451695, - 621.132194020027, - 618.3158878649041, - 616.9114498037155 - ], - "num_images_total": 50000, - "num_images_decoded": 50000, - "num_images_skipped": 0, - "skip_rate": 0.0, - "skip_indices": [], - "skip_examples": [] - }, - "num_images": 50000, - "num_runs": 5 -} \ No newline at end of file diff --git a/output/linux_AMD-EPYC-9B14/turbojpeg_1t_results.json b/output/linux_AMD-EPYC-9B14/turbojpeg_1t_results.json deleted file mode 100644 index fcae9a5..0000000 --- a/output/linux_AMD-EPYC-9B14/turbojpeg_1t_results.json +++ /dev/null @@ -1,57 +0,0 @@ -{ - "library": "turbojpeg", - "mode": "memory", - "run_tag": "1t", - "requested_threads": 1, - "effective_threads": 1, - "system_info": { - "Python": "3.12.3", - "OS": "Linux", - "OS Version": "#12~24.04.1-Ubuntu SMP Fri Mar 27 23:35:04 UTC 2026", - "Machine": "x86_64", - "CPU": { - "brand_raw": "AMD EPYC 9B14", - "arch": "X86_64", - "hz_advertised_raw": "Unknown", - "count": 16 - }, - "turbojpeg": "1.8.3" - }, - "benchmark_results": { - "images_per_second_mean": 639.5866403890547, - "images_per_second_std": 2.1731020718500726, - "images_per_second_p50": 638.4774027300007, - "images_per_second_p90": 642.1586312572418, - "images_per_second_p99": 643.5547019287616, - "us_per_image_mean": 1563.5278091361702, - "us_per_image_p50": 1566.2261432028786, - "us_per_image_p90": 1567.537818272361, - "us_per_image_p99": 1567.66092124404, - "raw_times_s": [ - 77.6731974209979, - 78.36506499999814, - 78.14396899699932, - 78.38216229300087, - 78.30974093400073 - ], - "raw_throughput_ips": [ - 643.7098208922638, - 638.0266512890813, - 639.831846804709, - 637.8874802292186, - 638.4774027300007 - ], - "num_images_total": 50000, - "num_images_decoded": 49999, - "num_images_skipped": 1, - "skip_rate": 2e-05, - "skip_indices": [ - 19876 - ], - "skip_examples": [ - "idx=19876: OSError: Unsupported color conversion request" - ] - }, - "num_images": 50000, - "num_runs": 5 -} \ No newline at end of file diff --git a/output/linux_AMD-EPYC-9B14/turbojpeg_dataloader_results.json b/output/linux_AMD-EPYC-9B14/turbojpeg_dataloader_results.json deleted file mode 100644 index 66996b5..0000000 --- a/output/linux_AMD-EPYC-9B14/turbojpeg_dataloader_results.json +++ /dev/null @@ -1,125 +0,0 @@ -{ - "library": "turbojpeg", - "benchmark_type": "dataloader", - "batch_size": 32, - "num_threads": 1, - "system_info": { - "Python": "3.12.3", - "OS": "Linux", - "OS Version": "#12~24.04.1-Ubuntu SMP Fri Mar 27 23:35:04 UTC 2026", - "Machine": "x86_64", - "CPU": { - "brand_raw": "AMD EPYC 9B14", - "arch": "X86_64", - "hz_advertised_raw": "Unknown", - "count": 16 - }, - "turbojpeg": "1.8.3" - }, - "worker_results": [ - { - "num_workers": 0, - "num_warmup": 1, - "images_per_second_mean": 640.6764277477172, - "images_per_second_std": 0.31776189618564366, - "images_per_second_p50": 640.8018116400676, - "images_per_second_p90": 640.9503285268321, - "images_per_second_p99": 640.983744826354, - "us_per_image_mean": 1560.850692720521, - "us_per_image_p50": 1560.5449014580668, - "us_per_image_p90": 1561.6403789675655, - "us_per_image_p99": 1561.8868614072028, - "raw_times_s": [ - 78.00308632499946, - 78.09415050299867, - 78.02568452800188 - ], - "raw_throughput_ips": [ - 640.9874577485232, - 640.2400138545605, - 640.8018116400676 - ] - }, - { - "num_workers": 2, - "num_warmup": 1, - "images_per_second_mean": 1042.359258818943, - "images_per_second_std": 1.8493225670626854, - "images_per_second_p50": 1042.8237770907263, - "images_per_second_p90": 1044.0495043818007, - "images_per_second_p99": 1044.3252930222925, - "us_per_image_mean": 959.3651528963934, - "us_per_image_p50": 958.9347902958291, - "us_per_image_p90": 961.0931317506645, - "us_per_image_p99": 961.5787585780024, - "raw_times_s": [ - 48.08067422300155, - 47.8754400359976, - 47.945780580001156 - ], - "raw_throughput_ips": [ - 1039.8980631615339, - 1044.3559362045694, - 1042.8237770907263 - ] - }, - { - "num_workers": 4, - "num_warmup": 1, - "images_per_second_mean": 1534.5673499710904, - "images_per_second_std": 3.005525792764862, - "images_per_second_p50": 1534.4090089998815, - "images_per_second_p90": 1537.5417759509844, - "images_per_second_p99": 1538.2466485149826, - "us_per_image_mean": 651.6519691127415, - "us_per_image_p50": 651.7167157743645, - "us_per_image_p90": 652.8885313866467, - "us_per_image_p99": 653.1521898994102, - "raw_times_s": [ - 32.658421083000576, - 32.50223525600086, - 32.58518407200245 - ], - "raw_throughput_ips": [ - 1530.9680732246293, - 1538.3249676887601, - 1534.4090089998815 - ] - }, - { - "num_workers": 8, - "num_warmup": 1, - "images_per_second_mean": 1502.677247495204, - "images_per_second_std": 21.144091993056776, - "images_per_second_p50": 1517.5133266413231, - "images_per_second_p90": 1517.6971738468842, - "images_per_second_p99": 1517.7385394681353, - "us_per_image_mean": 665.6120086935058, - "us_per_image_p50": 658.9727961159173, - "us_per_image_p90": 674.9867289705779, - "us_per_image_p99": 678.5898638628765, - "raw_times_s": [ - 32.94299201600006, - 32.94798083299975, - 33.948831618999975 - ], - "raw_throughput_ips": [ - 1517.7431356482743, - 1517.5133266413231, - 1472.7752801960144 - ] - } - ], - "num_images": 49999, - "num_images_total": 50000, - "num_images_decoded": 49999, - "num_images_skipped": 1, - "skip_rate": 2e-05, - "skip_indices": [ - 19876 - ], - "skip_examples": [ - "idx=19876: OSError: Unsupported color conversion request" - ], - "num_runs": 3 -} \ No newline at end of file diff --git a/output/linux_AMD-EPYC-9B45/ajpegli_1t_results.json b/output/linux_AMD-EPYC-9B45/ajpegli_1t_results.json deleted file mode 100644 index 6a00d49..0000000 --- a/output/linux_AMD-EPYC-9B45/ajpegli_1t_results.json +++ /dev/null @@ -1,57 +0,0 @@ -{ - "library": "ajpegli", - "mode": "memory", - "run_tag": "1t", - "requested_threads": 1, - "effective_threads": 1, - "system_info": { - "Python": "3.12.3", - "OS": "Linux", - "OS Version": "#17~24.04.1-Ubuntu SMP Thu May 7 15:12:38 UTC 2026", - "Machine": "x86_64", - "CPU": { - "brand_raw": "AMD EPYC 9B45", - "arch": "X86_64", - "hz_advertised_raw": "Unknown", - "count": 16 - }, - "ajpegli": "1.0.0" - }, - "benchmark_results": { - "images_per_second_mean": 501.40607004730794, - "images_per_second_std": 1.4483472687335015, - "images_per_second_p50": 501.92741517795105, - "images_per_second_p90": 502.8401842340531, - "images_per_second_p99": 503.0168942122083, - "us_per_image_mean": 1994.408155383107, - "us_per_image_p50": 1992.319944598891, - "us_per_image_p90": 2001.4822450649003, - "us_per_image_p99": 2003.353835268704, - "raw_times_s": [ - 99.49145564899999, - 99.39437228099996, - 99.91614804, - 99.61400490999995, - 100.17608592499994 - ], - "raw_throughput_ips": [ - 502.5456676037944, - 503.03652865422555, - 500.4096032603621, - 501.92741517795105, - 499.1111355402063 - ], - "num_images_total": 50000, - "num_images_decoded": 49999, - "num_images_skipped": 1, - "skip_rate": 2e-05, - "skip_indices": [ - 19876 - ], - "skip_examples": [ - "idx=19876: DecodeError: jpegli decode failed: ./lib/jpegli/color_transform.cc:549: Unsupported color transform 4 -> 2" - ] - }, - "num_images": 50000, - "num_runs": 5 -} \ No newline at end of file diff --git a/output/linux_AMD-EPYC-9B45/ajpegli_dataloader_results.json b/output/linux_AMD-EPYC-9B45/ajpegli_dataloader_results.json deleted file mode 100644 index 87d5df0..0000000 --- a/output/linux_AMD-EPYC-9B45/ajpegli_dataloader_results.json +++ /dev/null @@ -1,125 +0,0 @@ -{ - "library": "ajpegli", - "benchmark_type": "dataloader", - "batch_size": 32, - "num_threads": 1, - "system_info": { - "Python": "3.12.3", - "OS": "Linux", - "OS Version": "#17~24.04.1-Ubuntu SMP Thu May 7 15:12:38 UTC 2026", - "Machine": "x86_64", - "CPU": { - "brand_raw": "AMD EPYC 9B45", - "arch": "X86_64", - "hz_advertised_raw": "Unknown", - "count": 16 - }, - "ajpegli": "1.0.0" - }, - "worker_results": [ - { - "num_workers": 0, - "num_warmup": 1, - "images_per_second_mean": 495.0257758269601, - "images_per_second_std": 0.9000896938219153, - "images_per_second_p50": 494.9755224640577, - "images_per_second_p90": 495.91704316212446, - "images_per_second_p99": 496.1288853191895, - "us_per_image_mean": 2020.1035066168, - "us_per_image_p50": 2020.3019232584666, - "us_per_image_p90": 2023.6595372587476, - "us_per_image_p99": 2024.415000408811, - "raw_times_s": [ - 100.77346728199996, - 101.22292253900014, - 101.01307586100006 - ], - "raw_throughput_ips": [ - 496.15242333664116, - 493.94938168018126, - 494.9755224640577 - ] - }, - { - "num_workers": 2, - "num_warmup": 1, - "images_per_second_mean": 917.8155570285107, - "images_per_second_std": 2.5808088732191092, - "images_per_second_p50": 916.7596429170272, - "images_per_second_p90": 920.4472714554936, - "images_per_second_p99": 921.2769878766486, - "us_per_image_mean": 1089.5521296359254, - "us_per_image_p50": 1090.798452709056, - "us_per_image_p90": 1092.1730186643738, - "us_per_image_p99": 1092.4822960043202, - "raw_times_s": [ - 54.62474049100001, - 54.53883183700009, - 54.265978460999804 - ], - "raw_throughput_ips": [ - 915.3178495783948, - 916.7596429170272, - 921.3691785901102 - ] - }, - { - "num_workers": 4, - "num_warmup": 1, - "images_per_second_mean": 1765.2533957377761, - "images_per_second_std": 10.440613074055774, - "images_per_second_p50": 1771.3857780950389, - "images_per_second_p90": 1773.332574046474, - "images_per_second_p99": 1773.770603135547, - "us_per_image_mean": 566.5108058027846, - "us_per_image_p50": 564.5297666753355, - "us_per_image_p90": 569.90384132483, - "us_per_image_p99": 571.1130081209662, - "raw_times_s": [ - 28.18720078199999, - 28.225923804000104, - 28.56179675200019 - ], - "raw_throughput_ips": [ - 1773.8192730343328, - 1771.3857780950389, - 1750.5551360839565 - ] - }, - { - "num_workers": 8, - "num_warmup": 1, - "images_per_second_mean": 2882.0378647531375, - "images_per_second_std": 26.94656360657679, - "images_per_second_p50": 2894.974301880136, - "images_per_second_p90": 2904.284906872454, - "images_per_second_p99": 2906.3797929957254, - "us_per_image_mean": 347.0072136376057, - "us_per_image_p50": 345.426209604193, - "us_per_image_p90": 350.3271055901117, - "us_per_image_p99": 351.4298071869435, - "raw_times_s": [ - 17.201811042999907, - 17.270965054000044, - 17.577264926999987 - ], - "raw_throughput_ips": [ - 2906.6125581205333, - 2894.974301880136, - 2844.526734258742 - ] - } - ], - "num_images": 49999, - "num_images_total": 50000, - "num_images_decoded": 49999, - "num_images_skipped": 1, - "skip_rate": 2e-05, - "skip_indices": [ - 19876 - ], - "skip_examples": [ - "idx=19876: DecodeError: jpegli decode failed: ./lib/jpegli/color_transform.cc:549: Unsupported color transform 4 -> 2" - ], - "num_runs": 3 -} \ No newline at end of file diff --git a/output/linux_AMD-EPYC-9B45/imagecodecs_1t_results.json b/output/linux_AMD-EPYC-9B45/imagecodecs_1t_results.json deleted file mode 100644 index c843617..0000000 --- a/output/linux_AMD-EPYC-9B45/imagecodecs_1t_results.json +++ /dev/null @@ -1,53 +0,0 @@ -{ - "library": "imagecodecs", - "mode": "memory", - "run_tag": "1t", - "requested_threads": 1, - "effective_threads": 1, - "system_info": { - "Python": "3.12.3", - "OS": "Linux", - "OS Version": "#12~24.04.1-Ubuntu SMP Fri Mar 27 23:35:04 UTC 2026", - "Machine": "x86_64", - "CPU": { - "brand_raw": "AMD EPYC 9B45", - "arch": "X86_64", - "hz_advertised_raw": "Unknown", - "count": 16 - }, - "imagecodecs": "2026.3.6" - }, - "benchmark_results": { - "images_per_second_mean": 774.5270599636176, - "images_per_second_std": 6.034492031481702, - "images_per_second_p50": 772.4925972959459, - "images_per_second_p90": 780.9221016572993, - "images_per_second_p99": 785.7041020770272, - "us_per_image_mean": 1291.188088928, - "us_per_image_p50": 1294.5107869000003, - "us_per_image_p90": 1298.46480006, - "us_per_image_p99": 1300.4894866079999, - "raw_times_s": [ - 64.75451612400002, - 65.03572258899999, - 64.72553934500002, - 64.68706138199997, - 63.594182792000026 - ], - "raw_throughput_ips": [ - 772.1469172011689, - 768.8082489062234, - 772.4925972959459, - 772.9521009577529, - 786.2354354569969 - ], - "num_images_total": 50000, - "num_images_decoded": 50000, - "num_images_skipped": 0, - "skip_rate": 0.0, - "skip_indices": [], - "skip_examples": [] - }, - "num_images": 50000, - "num_runs": 5 -} \ No newline at end of file diff --git a/output/linux_AMD-EPYC-9B45/imagecodecs_dataloader_results.json b/output/linux_AMD-EPYC-9B45/imagecodecs_dataloader_results.json deleted file mode 100644 index 2d11a79..0000000 --- a/output/linux_AMD-EPYC-9B45/imagecodecs_dataloader_results.json +++ /dev/null @@ -1,121 +0,0 @@ -{ - "library": "imagecodecs", - "benchmark_type": "dataloader", - "batch_size": 32, - "num_threads": 1, - "system_info": { - "Python": "3.12.3", - "OS": "Linux", - "OS Version": "#12~24.04.1-Ubuntu SMP Fri Mar 27 23:35:04 UTC 2026", - "Machine": "x86_64", - "CPU": { - "brand_raw": "AMD EPYC 9B45", - "arch": "X86_64", - "hz_advertised_raw": "Unknown", - "count": 16 - }, - "imagecodecs": "2026.3.6" - }, - "worker_results": [ - { - "num_workers": 0, - "num_warmup": 1, - "images_per_second_mean": 770.7064861221401, - "images_per_second_std": 2.2685246248481143, - "images_per_second_p50": 770.9792745865344, - "images_per_second_p90": 772.8665700057636, - "images_per_second_p99": 773.2912114750902, - "us_per_image_mean": 1297.5220612333328, - "us_per_image_p50": 1297.0517275399993, - "us_per_image_p90": 1301.3459212039993, - "us_per_image_p99": 1302.3121147783993, - "raw_times_s": [ - 64.85258637699997, - 64.65474932699999, - 65.12097348099996 - ], - "raw_throughput_ips": [ - 770.9792745865344, - 773.3383938605709, - 767.8017899193147 - ] - }, - { - "num_workers": 2, - "num_warmup": 1, - "images_per_second_mean": 1350.3659483400158, - "images_per_second_std": 19.080243379604223, - "images_per_second_p50": 1338.3825861802452, - "images_per_second_p90": 1369.512695248666, - "images_per_second_p99": 1376.5169697890608, - "us_per_image_mean": 740.6864408133341, - "us_per_image_p50": 747.1705103800014, - "us_per_image_p90": 748.4965530200006, - "us_per_image_p99": 748.7949126140005, - "raw_times_s": [ - 36.30303741900002, - 37.44140318400002, - 37.35852551900007 - ], - "raw_throughput_ips": [ - 1377.2952225157712, - 1335.420036324031, - 1338.3825861802452 - ] - }, - { - "num_workers": 4, - "num_warmup": 1, - "images_per_second_mean": 2358.386606088548, - "images_per_second_std": 9.277430996840522, - "images_per_second_p50": 2363.457980906131, - "images_per_second_p90": 2365.7558180156807, - "images_per_second_p99": 2366.2728313653297, - "us_per_image_mean": 424.0252690399984, - "us_per_image_p50": 423.10885494000104, - "us_per_image_p90": 425.71911079599886, - "us_per_image_p99": 426.30641836359837, - "raw_times_s": [ - 21.129763870999795, - 21.318583737999916, - 21.155442747000052 - ], - "raw_throughput_ips": [ - 2366.3302772930683, - 2345.3715600664445, - 2363.457980906131 - ] - }, - { - "num_workers": 8, - "num_warmup": 1, - "images_per_second_mean": 2475.7439720728357, - "images_per_second_std": 47.25992866741151, - "images_per_second_p50": 2457.2681090578562, - "images_per_second_p90": 2523.939810025371, - "images_per_second_p99": 2538.9409427430624, - "us_per_image_mean": 404.0647743266679, - "us_per_image_p50": 406.9559997599981, - "us_per_image_p90": 410.696569904002, - "us_per_image_p99": 411.53819818640284, - "raw_times_s": [ - 19.680330539000124, - 20.581585622000148, - 20.347799987999906 - ], - "raw_throughput_ips": [ - 2540.60773526725, - 2429.356071893402, - 2457.2681090578562 - ] - } - ], - "num_images": 50000, - "num_images_total": 50000, - "num_images_decoded": 50000, - "num_images_skipped": 0, - "skip_rate": 0.0, - "skip_indices": [], - "skip_examples": [], - "num_runs": 3 -} \ No newline at end of file diff --git a/output/linux_AMD-EPYC-9B45/imageio_1t_results.json b/output/linux_AMD-EPYC-9B45/imageio_1t_results.json deleted file mode 100644 index c7cafcc..0000000 --- a/output/linux_AMD-EPYC-9B45/imageio_1t_results.json +++ /dev/null @@ -1,53 +0,0 @@ -{ - "library": "imageio", - "mode": "memory", - "run_tag": "1t", - "requested_threads": 1, - "effective_threads": 1, - "system_info": { - "Python": "3.12.3", - "OS": "Linux", - "OS Version": "#12~24.04.1-Ubuntu SMP Fri Mar 27 23:35:04 UTC 2026", - "Machine": "x86_64", - "CPU": { - "brand_raw": "AMD EPYC 9B45", - "arch": "X86_64", - "hz_advertised_raw": "Unknown", - "count": 16 - }, - "imageio": "2.37.3" - }, - "benchmark_results": { - "images_per_second_mean": 598.6554468224907, - "images_per_second_std": 4.824306415316687, - "images_per_second_p50": 595.7011122106434, - "images_per_second_p90": 604.3934790440449, - "images_per_second_p99": 607.469372592176, - "us_per_image_mean": 1670.5173927520002, - "us_per_image_p50": 1678.694196639999, - "us_per_image_p90": 1680.1624834960003, - "us_per_image_p99": 1680.8561994136012, - "raw_times_s": [ - 83.95031451499995, - 84.04666394800006, - 83.93470983199995, - 83.43526483400001, - 82.26239505900003 - ], - "raw_throughput_ips": [ - 595.5903832982802, - 594.9076102644021, - 595.7011122106434, - 599.2669897971597, - 607.8111385419684 - ], - "num_images_total": 50000, - "num_images_decoded": 50000, - "num_images_skipped": 0, - "skip_rate": 0.0, - "skip_indices": [], - "skip_examples": [] - }, - "num_images": 50000, - "num_runs": 5 -} \ No newline at end of file diff --git a/output/linux_AMD-EPYC-9B45/imageio_dataloader_results.json b/output/linux_AMD-EPYC-9B45/imageio_dataloader_results.json deleted file mode 100644 index 82f85f8..0000000 --- a/output/linux_AMD-EPYC-9B45/imageio_dataloader_results.json +++ /dev/null @@ -1,121 +0,0 @@ -{ - "library": "imageio", - "benchmark_type": "dataloader", - "batch_size": 32, - "num_threads": 1, - "system_info": { - "Python": "3.12.3", - "OS": "Linux", - "OS Version": "#12~24.04.1-Ubuntu SMP Fri Mar 27 23:35:04 UTC 2026", - "Machine": "x86_64", - "CPU": { - "brand_raw": "AMD EPYC 9B45", - "arch": "X86_64", - "hz_advertised_raw": "Unknown", - "count": 16 - }, - "imageio": "2.37.3" - }, - "worker_results": [ - { - "num_workers": 0, - "num_warmup": 1, - "images_per_second_mean": 597.2317398682303, - "images_per_second_std": 2.5351277776270913, - "images_per_second_p50": 597.6309057312542, - "images_per_second_p90": 599.6203713823413, - "images_per_second_p99": 600.0680011538359, - "us_per_image_mean": 1674.4221209333348, - "us_per_image_p50": 1673.2735713800002, - "us_per_image_p90": 1681.577202292, - "us_per_image_p99": 1683.4455192472, - "raw_times_s": [ - 83.66367856900001, - 84.182655501, - 83.31698407000022 - ], - "raw_throughput_ips": [ - 597.6309057312542, - 593.9465760783236, - 600.117737795113 - ] - }, - { - "num_workers": 2, - "num_warmup": 1, - "images_per_second_mean": 1036.773237942081, - "images_per_second_std": 3.6395377633833967, - "images_per_second_p50": 1034.3133944817157, - "images_per_second_p90": 1040.3976158344485, - "images_per_second_p99": 1041.7665656388135, - "us_per_image_mean": 964.5429264266659, - "us_per_image_p50": 966.8249539600038, - "us_per_image_p90": 966.993803991998, - "us_per_image_p99": 967.0317952491966, - "raw_times_s": [ - 47.988390440999865, - 48.34124769800019, - 48.35180082499983 - ], - "raw_throughput_ips": [ - 1041.9186711726318, - 1034.3133944817157, - 1034.0876481718958 - ] - }, - { - "num_workers": 4, - "num_warmup": 1, - "images_per_second_mean": 1799.5492228502274, - "images_per_second_std": 6.6698874853987355, - "images_per_second_p50": 1798.262885569274, - "images_per_second_p90": 1806.2805666557506, - "images_per_second_p99": 1808.084544900208, - "us_per_image_mean": 555.7023452200004, - "us_per_image_p50": 556.0922199000015, - "us_per_image_p90": 557.6221552919997, - "us_per_image_p99": 557.9663907551992, - "raw_times_s": [ - 27.804610995000075, - 27.650508831000025, - 27.90023195699996 - ], - "raw_throughput_ips": [ - 1798.262885569274, - 1808.2849869273698, - 1792.0997960540387 - ] - }, - { - "num_workers": 8, - "num_warmup": 1, - "images_per_second_mean": 2324.0469261757275, - "images_per_second_std": 85.17038136389867, - "images_per_second_p50": 2270.69885207562, - "images_per_second_p90": 2409.5346266590004, - "images_per_second_p99": 2440.772675940261, - "us_per_image_mean": 430.8482212400031, - "us_per_image_p50": 440.39305303999754, - "us_per_image_p90": 442.50027577600304, - "us_per_image_p99": 442.97440089160426, - "raw_times_s": [ - 22.15135407300022, - 22.019652651999877, - 20.45622646100037 - ], - "raw_throughput_ips": [ - 2257.1983561467177, - 2270.69885207562, - 2444.2435703048454 - ] - } - ], - "num_images": 50000, - "num_images_total": 50000, - "num_images_decoded": 50000, - "num_images_skipped": 0, - "skip_rate": 0.0, - "skip_indices": [], - "skip_examples": [], - "num_runs": 3 -} \ No newline at end of file diff --git a/output/linux_AMD-EPYC-9B45/jpeg4py_1t_results.json b/output/linux_AMD-EPYC-9B45/jpeg4py_1t_results.json deleted file mode 100644 index 8f48125..0000000 --- a/output/linux_AMD-EPYC-9B45/jpeg4py_1t_results.json +++ /dev/null @@ -1,57 +0,0 @@ -{ - "library": "jpeg4py", - "mode": "memory", - "run_tag": "1t", - "requested_threads": 1, - "effective_threads": 1, - "system_info": { - "Python": "3.12.3", - "OS": "Linux", - "OS Version": "#12~24.04.1-Ubuntu SMP Fri Mar 27 23:35:04 UTC 2026", - "Machine": "x86_64", - "CPU": { - "brand_raw": "AMD EPYC 9B45", - "arch": "X86_64", - "hz_advertised_raw": "Unknown", - "count": 16 - }, - "jpeg4py": "0.1.4" - }, - "benchmark_results": { - "images_per_second_mean": 760.2445713773063, - "images_per_second_std": 2.096033421597264, - "images_per_second_p50": 759.822924941266, - "images_per_second_p90": 762.6354155165367, - "images_per_second_p99": 763.6879938701898, - "us_per_image_mean": 1315.376167995361, - "us_per_image_p50": 1316.0961155223101, - "us_per_image_p90": 1318.9513073461494, - "us_per_image_p99": 1320.0644748454997, - "raw_times_s": [ - 66.00808781800015, - 65.80348967999998, - 65.85348431300008, - 65.7119736579998, - 65.46042964900016 - ], - "raw_throughput_ips": [ - 757.4677839154957, - 759.822924941266, - 759.2460827487262, - 760.881118260448, - 763.8049470205957 - ], - "num_images_total": 50000, - "num_images_decoded": 49999, - "num_images_skipped": 1, - "skip_rate": 2e-05, - "skip_indices": [ - 19876 - ], - "skip_examples": [ - "idx=19876: JPEGRuntimeError: tjDecompress2() failed with error -1 and error string Unsupported color conversion request" - ] - }, - "num_images": 50000, - "num_runs": 5 -} \ No newline at end of file diff --git a/output/linux_AMD-EPYC-9B45/jpeg4py_dataloader_results.json b/output/linux_AMD-EPYC-9B45/jpeg4py_dataloader_results.json deleted file mode 100644 index 809faf4..0000000 --- a/output/linux_AMD-EPYC-9B45/jpeg4py_dataloader_results.json +++ /dev/null @@ -1,125 +0,0 @@ -{ - "library": "jpeg4py", - "benchmark_type": "dataloader", - "batch_size": 32, - "num_threads": 1, - "system_info": { - "Python": "3.12.3", - "OS": "Linux", - "OS Version": "#12~24.04.1-Ubuntu SMP Fri Mar 27 23:35:04 UTC 2026", - "Machine": "x86_64", - "CPU": { - "brand_raw": "AMD EPYC 9B45", - "arch": "X86_64", - "hz_advertised_raw": "Unknown", - "count": 16 - }, - "jpeg4py": "0.1.4" - }, - "worker_results": [ - { - "num_workers": 0, - "num_warmup": 1, - "images_per_second_mean": 757.4378170803162, - "images_per_second_std": 5.842398482771239, - "images_per_second_p50": 754.291426844627, - "images_per_second_p90": 763.360145750952, - "images_per_second_p99": 765.4006075048752, - "us_per_image_mean": 1320.318552571048, - "us_per_image_p50": 1325.7475352507026, - "us_per_image_p90": 1328.4212195563848, - "us_per_image_p99": 1329.0227985251634, - "raw_times_s": [ - 66.45315294199963, - 66.28605101499988, - 65.30461797299995 - ], - "raw_throughput_ips": [ - 752.3946989187882, - 754.291426844627, - 765.6273254775333 - ] - }, - { - "num_workers": 2, - "num_warmup": 1, - "images_per_second_mean": 1290.244226781123, - "images_per_second_std": 3.741279741740545, - "images_per_second_p50": 1288.3114153871245, - "images_per_second_p90": 1294.0431375762716, - "images_per_second_p99": 1295.3327750688297, - "us_per_image_mean": 775.05356884471, - "us_per_image_p50": 776.2098418568387, - "us_per_image_p90": 776.8690601612035, - "us_per_image_p99": 777.0173842796856, - "raw_times_s": [ - 38.595078079999894, - 38.80971588300008, - 38.850916203 - ], - "raw_throughput_ips": [ - 1295.4760681235584, - 1288.3114153871245, - 1286.9451968326855 - ] - }, - { - "num_workers": 4, - "num_warmup": 1, - "images_per_second_mean": 2324.7002469675076, - "images_per_second_std": 12.249185453364355, - "images_per_second_p50": 2318.731191909353, - "images_per_second_p90": 2337.160762276411, - "images_per_second_p99": 2341.307415608999, - "us_per_image_mean": 430.1748908711517, - "us_per_image_p50": 431.270344526893, - "us_per_image_p90": 432.0353266585366, - "us_per_image_p99": 432.2074476381564, - "raw_times_s": [ - 21.350960766999833, - 21.563085956000123, - 21.61089638300018 - ], - "raw_throughput_ips": [ - 2341.7681548681753, - 2318.731191909353, - 2313.6013941249935 - ] - }, - { - "num_workers": 8, - "num_warmup": 1, - "images_per_second_mean": 2453.039858427499, - "images_per_second_std": 1.4933390586100863, - "images_per_second_p50": 2452.9393254741008, - "images_per_second_p90": 2454.5214737531323, - "images_per_second_p99": 2454.8774571159147, - "us_per_image_mean": 407.6576118989081, - "us_per_image_p50": 407.6741685433746, - "us_per_image_p90": 407.8971710034212, - "us_per_image_p99": 407.9473465569317, - "raw_times_s": [ - 20.366879931000312, - 20.397238128000026, - 20.383300753000185 - ], - "raw_throughput_ips": [ - 2454.9170108228905, - 2451.2632389855057, - 2452.9393254741008 - ] - } - ], - "num_images": 49999, - "num_images_total": 50000, - "num_images_decoded": 49999, - "num_images_skipped": 1, - "skip_rate": 2e-05, - "skip_indices": [ - 19876 - ], - "skip_examples": [ - "idx=19876: JPEGRuntimeError: tjDecompress2() failed with error -1 and error string Unsupported color conversion request" - ], - "num_runs": 3 -} \ No newline at end of file diff --git a/output/linux_AMD-EPYC-9B45/kornia_1t_results.json b/output/linux_AMD-EPYC-9B45/kornia_1t_results.json deleted file mode 100644 index d0381bd..0000000 --- a/output/linux_AMD-EPYC-9B45/kornia_1t_results.json +++ /dev/null @@ -1,57 +0,0 @@ -{ - "library": "kornia", - "mode": "memory", - "run_tag": "1t", - "requested_threads": 1, - "effective_threads": 1, - "system_info": { - "Python": "3.12.3", - "OS": "Linux", - "OS Version": "#12~24.04.1-Ubuntu SMP Fri Mar 27 23:35:04 UTC 2026", - "Machine": "x86_64", - "CPU": { - "brand_raw": "AMD EPYC 9B45", - "arch": "X86_64", - "hz_advertised_raw": "Unknown", - "count": 16 - }, - "kornia": "0.1.10" - }, - "benchmark_results": { - "images_per_second_mean": 761.2623103819208, - "images_per_second_std": 7.836701200085712, - "images_per_second_p50": 758.1652632251029, - "images_per_second_p90": 769.6376923446178, - "images_per_second_p99": 776.0602817176674, - "us_per_image_mean": 1313.7448711134225, - "us_per_image_p50": 1318.9736440128822, - "us_per_image_p90": 1322.5571172063424, - "us_per_image_p99": 1323.2377620616376, - "raw_times_s": [ - 65.9473632270001, - 66.1643461499998, - 66.06981403300006, - 65.88061815900028, - 64.3675074849998 - ], - "raw_throughput_ips": [ - 758.1652632251029, - 755.6788951960368, - 756.7601140064792, - 758.9333767228684, - 776.7739027591174 - ], - "num_images_total": 50000, - "num_images_decoded": 49999, - "num_images_skipped": 1, - "skip_rate": 2e-05, - "skip_indices": [ - 19876 - ], - "skip_examples": [ - "idx=19876: ValueError: Unsupported number of channels: 4" - ] - }, - "num_images": 50000, - "num_runs": 5 -} \ No newline at end of file diff --git a/output/linux_AMD-EPYC-9B45/kornia_dataloader_results.json b/output/linux_AMD-EPYC-9B45/kornia_dataloader_results.json deleted file mode 100644 index e538ee7..0000000 --- a/output/linux_AMD-EPYC-9B45/kornia_dataloader_results.json +++ /dev/null @@ -1,125 +0,0 @@ -{ - "library": "kornia", - "benchmark_type": "dataloader", - "batch_size": 32, - "num_threads": 1, - "system_info": { - "Python": "3.12.3", - "OS": "Linux", - "OS Version": "#12~24.04.1-Ubuntu SMP Fri Mar 27 23:35:04 UTC 2026", - "Machine": "x86_64", - "CPU": { - "brand_raw": "AMD EPYC 9B45", - "arch": "X86_64", - "hz_advertised_raw": "Unknown", - "count": 16 - }, - "kornia": "0.1.10" - }, - "worker_results": [ - { - "num_workers": 0, - "num_warmup": 1, - "images_per_second_mean": 793.6121143565747, - "images_per_second_std": 31.691578533418962, - "images_per_second_p50": 801.7128453917761, - "images_per_second_p90": 822.5318342126989, - "images_per_second_p99": 827.2161066974064, - "us_per_image_mean": 1262.105156156449, - "us_per_image_p50": 1247.329396987928, - "us_per_image_p90": 1314.1636838016736, - "us_per_image_p99": 1329.2013983347665, - "raw_times_s": [ - 66.542281903, - 62.365222519999406, - 60.40448268499949 - ], - "raw_throughput_ips": [ - 751.3869162600184, - 801.7128453917761, - 827.7365814179295 - ] - }, - { - "num_workers": 2, - "num_warmup": 1, - "images_per_second_mean": 1398.998259157, - "images_per_second_std": 2.8081363294481516, - "images_per_second_p50": 1398.1546360833183, - "images_per_second_p90": 1401.855587221775, - "images_per_second_p99": 1402.688301227928, - "us_per_image_mean": 714.8000500343347, - "us_per_image_p50": 715.2284691493941, - "us_per_image_p90": 716.0872477329536, - "us_per_image_p99": 716.2804729142546, - "raw_times_s": [ - 35.64277405899975, - 35.76070822900056, - 35.8143808169998 - ], - "raw_throughput_ips": [ - 1402.7808250063893, - 1398.1546360833183, - 1396.059316381292 - ] - }, - { - "num_workers": 4, - "num_warmup": 1, - "images_per_second_mean": 1976.0512104670352, - "images_per_second_std": 12.694152151725978, - "images_per_second_p50": 1973.186761830378, - "images_per_second_p90": 1988.9024309938254, - "images_per_second_p99": 1992.438456555601, - "us_per_image_mean": 506.08060071201317, - "us_per_image_p50": 506.79439946798277, - "us_per_image_p90": 509.07791424228253, - "us_per_image_p99": 509.5917050665, - "raw_times_s": [ - 25.089428688000226, - 25.33921317899967, - 25.48192999799994 - ], - "raw_throughput_ips": [ - 1992.8313482846872, - 1973.186761830378, - 1962.13552128604 - ] - }, - { - "num_workers": 8, - "num_warmup": 1, - "images_per_second_mean": 2393.6304916972535, - "images_per_second_std": 48.047403657962775, - "images_per_second_p50": 2419.7880589859906, - "images_per_second_p90": 2431.8476385368876, - "images_per_second_p99": 2434.5610439358393, - "us_per_image_mean": 417.9460775415532, - "us_per_image_p50": 413.2593333066735, - "us_per_image_p90": 426.554342950868, - "us_per_image_p99": 429.5457201208118, - "raw_times_s": [ - 21.49347489000047, - 20.534629496999514, - 20.66255340600037 - ], - "raw_throughput_ips": [ - 2326.240882681158, - 2434.8625334246117, - 2419.7880589859906 - ] - } - ], - "num_images": 49999, - "num_images_total": 50000, - "num_images_decoded": 49999, - "num_images_skipped": 1, - "skip_rate": 2e-05, - "skip_indices": [ - 19876 - ], - "skip_examples": [ - "idx=19876: ValueError: Unsupported number of channels: 4" - ], - "num_runs": 3 -} \ No newline at end of file diff --git a/output/linux_AMD-EPYC-9B45/opencv_1t_results.json b/output/linux_AMD-EPYC-9B45/opencv_1t_results.json deleted file mode 100644 index c64dbbf..0000000 --- a/output/linux_AMD-EPYC-9B45/opencv_1t_results.json +++ /dev/null @@ -1,53 +0,0 @@ -{ - "library": "opencv", - "mode": "memory", - "run_tag": "1t", - "requested_threads": 1, - "effective_threads": 1, - "system_info": { - "Python": "3.12.3", - "OS": "Linux", - "OS Version": "#12~24.04.1-Ubuntu SMP Fri Mar 27 23:35:04 UTC 2026", - "Machine": "x86_64", - "CPU": { - "brand_raw": "AMD EPYC 9B45", - "arch": "X86_64", - "hz_advertised_raw": "Unknown", - "count": 16 - }, - "opencv": "4.13.0.92" - }, - "benchmark_results": { - "images_per_second_mean": 840.8900134110809, - "images_per_second_std": 1.3856026037104108, - "images_per_second_p50": 840.6305620727828, - "images_per_second_p90": 842.419767358708, - "images_per_second_p99": 843.3590110071342, - "us_per_image_mean": 1189.2193924079984, - "us_per_image_p50": 1189.5832070799952, - "us_per_image_p90": 1190.9418991560015, - "us_per_image_p99": 1191.346131405598, - "raw_times_s": [ - 59.27939694199995, - 59.46332956399965, - 59.51340893700035, - 59.47916035399976, - 59.56955230499989 - ], - "raw_throughput_ips": [ - 843.4633714125149, - 840.8543612779977, - 840.1467987311726, - 840.6305620727828, - 839.3549735609364 - ], - "num_images_total": 50000, - "num_images_decoded": 50000, - "num_images_skipped": 0, - "skip_rate": 0.0, - "skip_indices": [], - "skip_examples": [] - }, - "num_images": 50000, - "num_runs": 5 -} \ No newline at end of file diff --git a/output/linux_AMD-EPYC-9B45/opencv_dataloader_results.json b/output/linux_AMD-EPYC-9B45/opencv_dataloader_results.json deleted file mode 100644 index 52f379f..0000000 --- a/output/linux_AMD-EPYC-9B45/opencv_dataloader_results.json +++ /dev/null @@ -1,121 +0,0 @@ -{ - "library": "opencv", - "benchmark_type": "dataloader", - "batch_size": 32, - "num_threads": 16, - "system_info": { - "Python": "3.12.3", - "OS": "Linux", - "OS Version": "#12~24.04.1-Ubuntu SMP Fri Mar 27 23:35:04 UTC 2026", - "Machine": "x86_64", - "CPU": { - "brand_raw": "AMD EPYC 9B45", - "arch": "X86_64", - "hz_advertised_raw": "Unknown", - "count": 16 - }, - "opencv": "4.13.0.92" - }, - "worker_results": [ - { - "num_workers": 0, - "num_warmup": 1, - "images_per_second_mean": 839.4924148672504, - "images_per_second_std": 0.47042621581659455, - "images_per_second_p50": 839.7582018083564, - "images_per_second_p90": 839.8617977687964, - "images_per_second_p99": 839.8851068598954, - "us_per_image_mean": 1191.1963663066672, - "us_per_image_p50": 1190.818973660007, - "us_per_image_p90": 1191.8715975959967, - "us_per_image_p99": 1192.1084379815943, - "raw_times_s": [ - 59.53176858400002, - 59.54094868300035, - 59.60673767899971 - ], - "raw_throughput_ips": [ - 839.8876967589064, - 839.7582018083564, - 838.8313460344887 - ] - }, - { - "num_workers": 2, - "num_warmup": 1, - "images_per_second_mean": 1461.6950532768994, - "images_per_second_std": 0.8019097204486171, - "images_per_second_p50": 1461.5096729233067, - "images_per_second_p90": 1462.5072688263706, - "images_per_second_p99": 1462.73172790456, - "us_per_image_mean": 684.1374325533252, - "us_per_image_p50": 684.2240037999909, - "us_per_image_p90": 684.482871639997, - "us_per_image_p99": 684.5411169039984, - "raw_times_s": [ - 34.211200189999545, - 34.18203526299931, - 34.22737942999993 - ], - "raw_throughput_ips": [ - 1461.5096729233067, - 1462.7566678021367, - 1460.8188191052552 - ] - }, - { - "num_workers": 4, - "num_warmup": 1, - "images_per_second_mean": 2597.00454114611, - "images_per_second_std": 8.157206059079472, - "images_per_second_p50": 2598.430301693794, - "images_per_second_p90": 2604.6505093581795, - "images_per_second_p99": 2606.0500560826663, - "us_per_image_mean": 385.0628130133373, - "us_per_image_p50": 384.8477287800051, - "us_per_image_p90": 386.2824435960065, - "us_per_image_p99": 386.6052544296068, - "raw_times_s": [ - 19.33205611500034, - 19.184979397999996, - 19.242386439000256 - ], - "raw_throughput_ips": [ - 2586.377760470261, - 2606.205561274276, - 2598.430301693794 - ] - }, - { - "num_workers": 8, - "num_warmup": 1, - "images_per_second_mean": 2813.99708246655, - "images_per_second_std": 35.15646789307927, - "images_per_second_p50": 2819.1088151233957, - "images_per_second_p90": 2847.2383585715274, - "images_per_second_p99": 2853.567505847357, - "us_per_image_mean": 355.42202512666336, - "us_per_image_p50": 354.7220293999999, - "us_per_image_p90": 359.8978923919931, - "us_per_image_p99": 361.06246156519154, - "raw_times_s": [ - 17.51760939199994, - 18.05959290699957, - 17.736101469999994 - ], - "raw_throughput_ips": [ - 2854.2707444335606, - 2768.611687842693, - 2819.1088151233957 - ] - } - ], - "num_images": 50000, - "num_images_total": 50000, - "num_images_decoded": 50000, - "num_images_skipped": 0, - "skip_rate": 0.0, - "skip_indices": [], - "skip_examples": [], - "num_runs": 3 -} \ No newline at end of file diff --git a/output/linux_AMD-EPYC-9B45/opencv_default_results.json b/output/linux_AMD-EPYC-9B45/opencv_default_results.json deleted file mode 100644 index f644669..0000000 --- a/output/linux_AMD-EPYC-9B45/opencv_default_results.json +++ /dev/null @@ -1,53 +0,0 @@ -{ - "library": "opencv", - "mode": "memory", - "run_tag": "default", - "requested_threads": 0, - "effective_threads": 16, - "system_info": { - "Python": "3.12.3", - "OS": "Linux", - "OS Version": "#12~24.04.1-Ubuntu SMP Fri Mar 27 23:35:04 UTC 2026", - "Machine": "x86_64", - "CPU": { - "brand_raw": "AMD EPYC 9B45", - "arch": "X86_64", - "hz_advertised_raw": "Unknown", - "count": 16 - }, - "opencv": "4.13.0.92" - }, - "benchmark_results": { - "images_per_second_mean": 838.282493658711, - "images_per_second_std": 2.1251548529759003, - "images_per_second_p50": 837.4325430908765, - "images_per_second_p90": 840.8466931205237, - "images_per_second_p99": 841.8287624680262, - "us_per_image_mean": 1192.9229389599968, - "us_per_image_p50": 1194.1260322999915, - "us_per_image_p90": 1195.7432557279972, - "us_per_image_p99": 1196.3389044847959, - "raw_times_s": [ - 59.38680407599986, - 59.706301614999575, - 59.73752538999997, - 59.82025438399978, - 59.579849275000015 - ], - "raw_throughput_ips": [ - 841.9378812844153, - 837.4325430908765, - 836.9948315329776, - 835.8373015105997, - 839.2099108746862 - ], - "num_images_total": 50000, - "num_images_decoded": 50000, - "num_images_skipped": 0, - "skip_rate": 0.0, - "skip_indices": [], - "skip_examples": [] - }, - "num_images": 50000, - "num_runs": 5 -} \ No newline at end of file diff --git a/output/linux_AMD-EPYC-9B45/pillow_1t_results.json b/output/linux_AMD-EPYC-9B45/pillow_1t_results.json deleted file mode 100644 index a3dd4be..0000000 --- a/output/linux_AMD-EPYC-9B45/pillow_1t_results.json +++ /dev/null @@ -1,53 +0,0 @@ -{ - "library": "pillow", - "mode": "memory", - "run_tag": "1t", - "requested_threads": 1, - "effective_threads": 1, - "system_info": { - "Python": "3.12.3", - "OS": "Linux", - "OS Version": "#12~24.04.1-Ubuntu SMP Fri Mar 27 23:35:04 UTC 2026", - "Machine": "x86_64", - "CPU": { - "brand_raw": "AMD EPYC 9B45", - "arch": "X86_64", - "hz_advertised_raw": "Unknown", - "count": 16 - }, - "pillow": "12.2.0" - }, - "benchmark_results": { - "images_per_second_mean": 726.232289306106, - "images_per_second_std": 2.214021073763305, - "images_per_second_p50": 725.4415705831312, - "images_per_second_p90": 728.8898502064362, - "images_per_second_p99": 729.9734413163987, - "us_per_image_mean": 1376.982665980002, - "us_per_image_p50": 1378.4707694600002, - "us_per_image_p90": 1380.9000386760054, - "us_per_image_p99": 1381.6229546616064, - "raw_times_s": [ - 68.98475893500017, - 68.92353847300001, - 68.76785802199993, - 68.48434713200004, - 69.08516393300033 - ], - "raw_throughput_ips": [ - 724.7977781166378, - 725.4415705831312, - 727.0838650231655, - 730.0938403286168, - 723.7443924789791 - ], - "num_images_total": 50000, - "num_images_decoded": 50000, - "num_images_skipped": 0, - "skip_rate": 0.0, - "skip_indices": [], - "skip_examples": [] - }, - "num_images": 50000, - "num_runs": 5 -} \ No newline at end of file diff --git a/output/linux_AMD-EPYC-9B45/pillow_dataloader_results.json b/output/linux_AMD-EPYC-9B45/pillow_dataloader_results.json deleted file mode 100644 index b8aabd0..0000000 --- a/output/linux_AMD-EPYC-9B45/pillow_dataloader_results.json +++ /dev/null @@ -1,121 +0,0 @@ -{ - "library": "pillow", - "benchmark_type": "dataloader", - "batch_size": 32, - "num_threads": 1, - "system_info": { - "Python": "3.12.3", - "OS": "Linux", - "OS Version": "#12~24.04.1-Ubuntu SMP Fri Mar 27 23:35:04 UTC 2026", - "Machine": "x86_64", - "CPU": { - "brand_raw": "AMD EPYC 9B45", - "arch": "X86_64", - "hz_advertised_raw": "Unknown", - "count": 16 - }, - "pillow": "12.2.0" - }, - "worker_results": [ - { - "num_workers": 0, - "num_warmup": 1, - "images_per_second_mean": 704.5182931132904, - "images_per_second_std": 0.4541765511265039, - "images_per_second_p50": 704.5171457297363, - "images_per_second_p90": 704.9635222013435, - "images_per_second_p99": 705.0639569074551, - "us_per_image_mean": 1419.4101492666657, - "us_per_image_p50": 1419.4118710399925, - "us_per_image_p90": 1420.3063551840023, - "us_per_image_p99": 1420.5076141164045, - "raw_times_s": [ - 71.02649881100024, - 70.91443002699998, - 70.97059355199963 - ], - "raw_throughput_ips": [ - 703.9626172908899, - 705.0751163192454, - 704.5171457297363 - ] - }, - { - "num_workers": 2, - "num_warmup": 1, - "images_per_second_mean": 1210.647051039481, - "images_per_second_std": 0.14569080737150292, - "images_per_second_p50": 1210.7098151058506, - "images_per_second_p90": 1210.7704610940991, - "images_per_second_p99": 1210.784106441455, - "us_per_image_mean": 826.0045845933322, - "us_per_image_p50": 825.9617519600033, - "us_per_image_p90": 826.1059211599968, - "us_per_image_p99": 826.1383592299953, - "raw_times_s": [ - 41.2955019179999, - 41.30709817299976, - 41.298087598000166 - ], - "raw_throughput_ips": [ - 1210.7856225911612, - 1210.4457154214315, - 1210.7098151058506 - ] - }, - { - "num_workers": 4, - "num_warmup": 1, - "images_per_second_mean": 2140.573850886185, - "images_per_second_std": 7.957188003396283, - "images_per_second_p50": 2136.3343149518146, - "images_per_second_p90": 2148.64372512601, - "images_per_second_p99": 2151.4133424152037, - "us_per_image_mean": 467.1708873866737, - "us_per_image_p50": 468.0915309000011, - "us_per_image_p90": 468.5598106120051, - "us_per_image_p99": 468.665173547206, - "raw_times_s": [ - 23.404576545000054, - 23.433844027000305, - 23.237212536000698 - ], - "raw_throughput_ips": [ - 2136.3343149518146, - 2133.666160037182, - 2151.721077669559 - ] - }, - { - "num_workers": 8, - "num_warmup": 1, - "images_per_second_mean": 2464.8207003090497, - "images_per_second_std": 9.986103568705232, - "images_per_second_p50": 2469.7958159451878, - "images_per_second_p90": 2472.982779432037, - "images_per_second_p99": 2473.6998462165784, - "us_per_image_mean": 405.71570013999616, - "us_per_image_p50": 404.8917702200015, - "us_per_image_p90": 407.3908230039997, - "us_per_image_p99": 407.95310988039927, - "raw_times_s": [ - 20.211987199999385, - 20.244588511000075, - 20.40077930999996 - ], - "raw_throughput_ips": [ - 2473.7795203037495, - 2469.7958159451878, - 2450.8867646782114 - ] - } - ], - "num_images": 50000, - "num_images_total": 50000, - "num_images_decoded": 50000, - "num_images_skipped": 0, - "skip_rate": 0.0, - "skip_indices": [], - "skip_examples": [], - "num_runs": 3 -} \ No newline at end of file diff --git a/output/linux_AMD-EPYC-9B45/pyvips_1t_results.json b/output/linux_AMD-EPYC-9B45/pyvips_1t_results.json deleted file mode 100644 index a641320..0000000 --- a/output/linux_AMD-EPYC-9B45/pyvips_1t_results.json +++ /dev/null @@ -1,53 +0,0 @@ -{ - "library": "pyvips", - "mode": "memory", - "run_tag": "1t", - "requested_threads": 1, - "effective_threads": 1, - "system_info": { - "Python": "3.12.3", - "OS": "Linux", - "OS Version": "#12~24.04.1-Ubuntu SMP Fri Mar 27 23:35:04 UTC 2026", - "Machine": "x86_64", - "CPU": { - "brand_raw": "AMD EPYC 9B45", - "arch": "X86_64", - "hz_advertised_raw": "Unknown", - "count": 16 - }, - "pyvips": "3.1.1" - }, - "benchmark_results": { - "images_per_second_mean": 586.0081054153176, - "images_per_second_std": 1.3409706480704455, - "images_per_second_p50": 586.6775807626818, - "images_per_second_p90": 587.1578390816225, - "images_per_second_p99": 587.1620733513789, - "us_per_image_mean": 1706.4699908199982, - "us_per_image_p50": 1704.5137445, - "us_per_image_p90": 1711.3027563319956, - "us_per_image_p99": 1713.1301971231921, - "raw_times_s": [ - 85.15700146499967, - 85.15529562600022, - 85.66666230499959, - 85.41285108400007, - 85.225687225 - ], - "raw_throughput_ips": [ - 587.1507819653616, - 587.1625438257963, - 583.6576172652165, - 585.3920032575313, - 586.6775807626818 - ], - "num_images_total": 50000, - "num_images_decoded": 50000, - "num_images_skipped": 0, - "skip_rate": 0.0, - "skip_indices": [], - "skip_examples": [] - }, - "num_images": 50000, - "num_runs": 5 -} \ No newline at end of file diff --git a/output/linux_AMD-EPYC-9B45/pyvips_default_results.json b/output/linux_AMD-EPYC-9B45/pyvips_default_results.json deleted file mode 100644 index e3a3bb6..0000000 --- a/output/linux_AMD-EPYC-9B45/pyvips_default_results.json +++ /dev/null @@ -1,53 +0,0 @@ -{ - "library": "pyvips", - "mode": "memory", - "run_tag": "default", - "requested_threads": 0, - "effective_threads": 16, - "system_info": { - "Python": "3.12.3", - "OS": "Linux", - "OS Version": "#12~24.04.1-Ubuntu SMP Fri Mar 27 23:35:04 UTC 2026", - "Machine": "x86_64", - "CPU": { - "brand_raw": "AMD EPYC 9B45", - "arch": "X86_64", - "hz_advertised_raw": "Unknown", - "count": 16 - }, - "pyvips": "3.1.1" - }, - "benchmark_results": { - "images_per_second_mean": 530.7141246476422, - "images_per_second_std": 0.5216986552906722, - "images_per_second_p50": 530.7715097864653, - "images_per_second_p90": 531.2752408717174, - "images_per_second_p99": 531.2802243292927, - "us_per_image_mean": 1884.2554212120085, - "us_per_image_p50": 1884.049881280007, - "us_per_image_p90": 1886.4437716160173, - "us_per_image_p99": 1886.7986403056116, - "raw_times_s": [ - 94.11464688600063, - 94.11219465499926, - 94.20249406400035, - 94.34190350800054, - 94.29261619000135 - ], - "raw_throughput_ips": [ - 531.266935109092, - 531.2807780468011, - 530.7715097864653, - 529.9871864018497, - 530.2642138940029 - ], - "num_images_total": 50000, - "num_images_decoded": 50000, - "num_images_skipped": 0, - "skip_rate": 0.0, - "skip_indices": [], - "skip_examples": [] - }, - "num_images": 50000, - "num_runs": 5 -} \ No newline at end of file diff --git a/output/linux_AMD-EPYC-9B45/run_summary.json b/output/linux_AMD-EPYC-9B45/run_summary.json deleted file mode 100644 index f5768c9..0000000 --- a/output/linux_AMD-EPYC-9B45/run_summary.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "timestamp_utc": "2026-05-20T07:56:38.744173+00:00", - "system": "linux_AMD-EPYC-9B45", - "mode": "both", - "num_images": 50000, - "num_runs": 5, - "dataloader_runs": 3, - "workers": [ - 0, - 2, - 4, - 8 - ], - "libs_requested": [ - "ajpegli" - ], - "libs_skipped_platform": [], - "libs_run": [ - "ajpegli" - ], - "failures": [], - "exit_status": "ok" -} \ No newline at end of file diff --git a/output/linux_AMD-EPYC-9B45/simplejpeg_1t_results.json b/output/linux_AMD-EPYC-9B45/simplejpeg_1t_results.json deleted file mode 100644 index c049cc9..0000000 --- a/output/linux_AMD-EPYC-9B45/simplejpeg_1t_results.json +++ /dev/null @@ -1,53 +0,0 @@ -{ - "library": "simplejpeg", - "mode": "memory", - "run_tag": "1t", - "requested_threads": 1, - "effective_threads": 1, - "system_info": { - "Python": "3.12.3", - "OS": "Linux", - "OS Version": "#12~24.04.1-Ubuntu SMP Fri Mar 27 23:35:04 UTC 2026", - "Machine": "x86_64", - "CPU": { - "brand_raw": "AMD EPYC 9B45", - "arch": "X86_64", - "hz_advertised_raw": "Unknown", - "count": 16 - }, - "simplejpeg": "1.9.0" - }, - "benchmark_results": { - "images_per_second_mean": 856.6874667125096, - "images_per_second_std": 0.7296964564958558, - "images_per_second_p50": 856.6167790468982, - "images_per_second_p90": 857.5500282121479, - "images_per_second_p99": 857.819439942204, - "us_per_image_mean": 1167.287679711997, - "us_per_image_p50": 1167.3831571600022, - "us_per_image_p90": 1168.3252405399908, - "us_per_image_p99": 1168.3696636400011, - "raw_times_s": [ - 58.41256010199868, - 58.3361814939999, - 58.36915785800011, - 58.41872997700011, - 58.28529049700046 - ], - "raw_throughput_ips": [ - 855.9802876759919, - 857.1010086620546, - 856.6167790468982, - 855.8898835987254, - 857.8493745788769 - ], - "num_images_total": 50000, - "num_images_decoded": 50000, - "num_images_skipped": 0, - "skip_rate": 0.0, - "skip_indices": [], - "skip_examples": [] - }, - "num_images": 50000, - "num_runs": 5 -} \ No newline at end of file diff --git a/output/linux_AMD-EPYC-9B45/simplejpeg_dataloader_results.json b/output/linux_AMD-EPYC-9B45/simplejpeg_dataloader_results.json deleted file mode 100644 index 8764f33..0000000 --- a/output/linux_AMD-EPYC-9B45/simplejpeg_dataloader_results.json +++ /dev/null @@ -1,121 +0,0 @@ -{ - "library": "simplejpeg", - "benchmark_type": "dataloader", - "batch_size": 32, - "num_threads": 1, - "system_info": { - "Python": "3.12.3", - "OS": "Linux", - "OS Version": "#12~24.04.1-Ubuntu SMP Fri Mar 27 23:35:04 UTC 2026", - "Machine": "x86_64", - "CPU": { - "brand_raw": "AMD EPYC 9B45", - "arch": "X86_64", - "hz_advertised_raw": "Unknown", - "count": 16 - }, - "simplejpeg": "1.9.0" - }, - "worker_results": [ - { - "num_workers": 0, - "num_warmup": 1, - "images_per_second_mean": 851.5039507518637, - "images_per_second_std": 0.7944418358710506, - "images_per_second_p50": 851.2477147102661, - "images_per_second_p90": 852.3130745999027, - "images_per_second_p99": 852.552780575071, - "us_per_image_mean": 1174.393693906665, - "us_per_image_p50": 1174.746178719979, - "us_per_image_p90": 1175.3681458240098, - "us_per_image_p99": 1175.5080884224167, - "raw_times_s": [ - 58.73730893599895, - 58.64556326999991, - 58.77618188000088 - ], - "raw_throughput_ips": [ - 851.2477147102661, - 852.5794145723119, - 850.6847229730134 - ] - }, - { - "num_workers": 2, - "num_warmup": 1, - "images_per_second_mean": 1468.5080372047075, - "images_per_second_std": 2.306039259709732, - "images_per_second_p50": 1467.3383030819734, - "images_per_second_p90": 1470.8510907084062, - "images_per_second_p99": 1471.6414679243535, - "us_per_image_mean": 680.9649236933183, - "us_per_image_p50": 681.5060970599734, - "us_per_image_p90": 681.8339294439793, - "us_per_image_p99": 681.9076917303806, - "raw_times_s": [ - 33.97363932400003, - 34.09579437699904, - 34.07530485299867 - ], - "raw_throughput_ips": [ - 1471.7292876150143, - 1466.4565209171344, - 1467.3383030819734 - ] - }, - { - "num_workers": 4, - "num_warmup": 1, - "images_per_second_mean": 2659.0594210537656, - "images_per_second_std": 3.9680186863262215, - "images_per_second_p50": 2656.682035053833, - "images_per_second_p90": 2663.056605594056, - "images_per_second_p99": 2664.490883965606, - "us_per_image_mean": 376.07366600001114, - "us_per_image_p50": 376.40936581999995, - "us_per_image_p90": 376.50416017201496, - "us_per_image_p99": 376.52548890121835, - "raw_times_s": [ - 18.76418867100074, - 18.826392938000936, - 18.820468290999997 - ], - "raw_throughput_ips": [ - 2664.6502482291116, - 2655.845979878353, - 2656.682035053833 - ] - }, - { - "num_workers": 8, - "num_warmup": 1, - "images_per_second_mean": 2738.6566679517505, - "images_per_second_std": 22.124318156355525, - "images_per_second_p50": 2732.8568527341995, - "images_per_second_p90": 2761.1182709457566, - "images_per_second_p99": 2767.4770900433573, - "us_per_image_mean": 365.16628129332577, - "us_per_image_p50": 365.9174460599752, - "us_per_image_p90": 367.8504381879902, - "us_per_image_p99": 368.2853614167936, - "raw_times_s": [ - 18.06238558000041, - 18.4166843109997, - 18.29587230299876 - ], - "raw_throughput_ips": [ - 2768.183625498646, - 2714.929525622405, - 2732.8568527341995 - ] - } - ], - "num_images": 50000, - "num_images_total": 50000, - "num_images_decoded": 50000, - "num_images_skipped": 0, - "skip_rate": 0.0, - "skip_indices": [], - "skip_examples": [], - "num_runs": 3 -} \ No newline at end of file diff --git a/output/linux_AMD-EPYC-9B45/skimage_1t_results.json b/output/linux_AMD-EPYC-9B45/skimage_1t_results.json deleted file mode 100644 index c9124eb..0000000 --- a/output/linux_AMD-EPYC-9B45/skimage_1t_results.json +++ /dev/null @@ -1,53 +0,0 @@ -{ - "library": "skimage", - "mode": "memory", - "run_tag": "1t", - "requested_threads": 1, - "effective_threads": 1, - "system_info": { - "Python": "3.12.3", - "OS": "Linux", - "OS Version": "#12~24.04.1-Ubuntu SMP Fri Mar 27 23:35:04 UTC 2026", - "Machine": "x86_64", - "CPU": { - "brand_raw": "AMD EPYC 9B45", - "arch": "X86_64", - "hz_advertised_raw": "Unknown", - "count": 16 - }, - "skimage": "0.26.0" - }, - "benchmark_results": { - "images_per_second_mean": 661.3116591001643, - "images_per_second_std": 2.5605286265496985, - "images_per_second_p50": 662.0191217546001, - "images_per_second_p90": 663.4371510850142, - "images_per_second_p99": 663.8567344293873, - "us_per_image_mean": 1512.169100603991, - "us_per_image_p50": 1510.5303867199837, - "us_per_image_p90": 1518.7350872839743, - "us_per_image_p99": 1522.8582394063749, - "raw_times_s": [ - 75.44461258500087, - 76.16581837099875, - 75.59315835399866, - 75.52651933599918, - 75.31216650500028 - ], - "raw_throughput_ips": [ - 662.7378455110589, - 656.4624534913188, - 661.4355199428593, - 662.0191217546001, - 663.9033548009843 - ], - "num_images_total": 50000, - "num_images_decoded": 50000, - "num_images_skipped": 0, - "skip_rate": 0.0, - "skip_indices": [], - "skip_examples": [] - }, - "num_images": 50000, - "num_runs": 5 -} \ No newline at end of file diff --git a/output/linux_AMD-EPYC-9B45/skimage_dataloader_results.json b/output/linux_AMD-EPYC-9B45/skimage_dataloader_results.json deleted file mode 100644 index ccb24e1..0000000 --- a/output/linux_AMD-EPYC-9B45/skimage_dataloader_results.json +++ /dev/null @@ -1,121 +0,0 @@ -{ - "library": "skimage", - "benchmark_type": "dataloader", - "batch_size": 32, - "num_threads": 1, - "system_info": { - "Python": "3.12.3", - "OS": "Linux", - "OS Version": "#12~24.04.1-Ubuntu SMP Fri Mar 27 23:35:04 UTC 2026", - "Machine": "x86_64", - "CPU": { - "brand_raw": "AMD EPYC 9B45", - "arch": "X86_64", - "hz_advertised_raw": "Unknown", - "count": 16 - }, - "skimage": "0.26.0" - }, - "worker_results": [ - { - "num_workers": 0, - "num_warmup": 1, - "images_per_second_mean": 644.0755378905928, - "images_per_second_std": 6.381949145267184, - "images_per_second_p50": 647.5177412562664, - "images_per_second_p90": 649.167468933183, - "images_per_second_p99": 649.5386576604892, - "us_per_image_mean": 1552.766364333329, - "us_per_image_p50": 1544.3592295399867, - "us_per_image_p90": 1568.4585374600138, - "us_per_image_p99": 1573.88088174202, - "raw_times_s": [ - 77.21796147699933, - 78.72416822200103, - 76.97282495099898 - ], - "raw_throughput_ips": [ - 647.5177412562664, - 635.1289715631001, - 649.5799008524122 - ] - }, - { - "num_workers": 2, - "num_warmup": 1, - "images_per_second_mean": 1097.797690499958, - "images_per_second_std": 8.04060908464861, - "images_per_second_p50": 1092.5795385784845, - "images_per_second_p90": 1105.8409898854106, - "images_per_second_p99": 1108.824816429469, - "us_per_image_mean": 910.9632706800161, - "us_per_image_p50": 915.265172640029, - "us_per_image_p90": 915.883830224011, - "us_per_image_p99": 916.023028180407, - "raw_times_s": [ - 45.80192473100033, - 45.76325863200145, - 45.07930723900063 - ], - "raw_throughput_ips": [ - 1091.6571802092471, - 1092.5795385784845, - 1109.156352712142 - ] - }, - { - "num_workers": 4, - "num_warmup": 1, - "images_per_second_mean": 1985.8287354743663, - "images_per_second_std": 14.725038709957023, - "images_per_second_p50": 1991.2030827772423, - "images_per_second_p90": 1998.6926450895216, - "images_per_second_p99": 2000.3777966097846, - "us_per_image_mean": 503.5958908199973, - "us_per_image_p50": 502.2089452599903, - "us_per_image_p90": 507.4177460280044, - "us_per_image_p99": 508.58972620080755, - "raw_times_s": [ - 24.992939048999688, - 25.110447262999514, - 25.435997311000392 - ], - "raw_throughput_ips": [ - 2000.5650356675915, - 1991.2030827772423, - 1965.7180879782657 - ] - }, - { - "num_workers": 8, - "num_warmup": 1, - "images_per_second_mean": 2535.736484479317, - "images_per_second_std": 26.548516337174608, - "images_per_second_p50": 2544.3623056932906, - "images_per_second_p90": 2559.327648385399, - "images_per_second_p99": 2562.6948504911234, - "us_per_image_mean": 394.4061852733527, - "us_per_image_p50": 393.0257879400233, - "us_per_image_p90": 398.6335551560114, - "us_per_image_p99": 399.89530277960876, - "raw_times_s": [ - 19.507863546001317, - 19.651289397001165, - 20.001774848000423 - ], - "raw_throughput_ips": [ - 2563.068984058426, - 2544.3623056932906, - 2499.778163686234 - ] - } - ], - "num_images": 50000, - "num_images_total": 50000, - "num_images_decoded": 50000, - "num_images_skipped": 0, - "skip_rate": 0.0, - "skip_indices": [], - "skip_examples": [], - "num_runs": 3 -} \ No newline at end of file diff --git a/output/linux_AMD-EPYC-9B45/tensorflow_1t_results.json b/output/linux_AMD-EPYC-9B45/tensorflow_1t_results.json deleted file mode 100644 index ddf8f7d..0000000 --- a/output/linux_AMD-EPYC-9B45/tensorflow_1t_results.json +++ /dev/null @@ -1,53 +0,0 @@ -{ - "library": "tensorflow", - "mode": "memory", - "run_tag": "1t", - "requested_threads": 1, - "effective_threads": 1, - "system_info": { - "Python": "3.12.3", - "OS": "Linux", - "OS Version": "#12~24.04.1-Ubuntu SMP Fri Mar 27 23:35:04 UTC 2026", - "Machine": "x86_64", - "CPU": { - "brand_raw": "AMD EPYC 9B45", - "arch": "X86_64", - "hz_advertised_raw": "Unknown", - "count": 16 - }, - "tensorflow": "2.21.0" - }, - "benchmark_results": { - "images_per_second_mean": 835.9019618905286, - "images_per_second_std": 1.3236132643631149, - "images_per_second_p50": 835.2497267984606, - "images_per_second_p90": 837.407313679492, - "images_per_second_p99": 838.3645680110981, - "us_per_image_mean": 1196.3155343639955, - "us_per_image_p50": 1197.246725039986, - "us_per_image_p90": 1197.6701213039996, - "us_per_image_p99": 1197.846432830389, - "raw_times_s": [ - 59.89330114999939, - 59.86881343800087, - 59.862336251999295, - 59.63235961399914, - 59.82207313700019 - ], - "raw_throughput_ips": [ - 834.8179018347615, - 835.1593614224399, - 835.2497267984606, - 838.4709296034988, - 835.8118897934817 - ], - "num_images_total": 50000, - "num_images_decoded": 50000, - "num_images_skipped": 0, - "skip_rate": 0.0, - "skip_indices": [], - "skip_examples": [] - }, - "num_images": 50000, - "num_runs": 5 -} \ No newline at end of file diff --git a/output/linux_AMD-EPYC-9B45/torchvision_1t_results.json b/output/linux_AMD-EPYC-9B45/torchvision_1t_results.json deleted file mode 100644 index 0bc7568..0000000 --- a/output/linux_AMD-EPYC-9B45/torchvision_1t_results.json +++ /dev/null @@ -1,53 +0,0 @@ -{ - "library": "torchvision", - "mode": "memory", - "run_tag": "1t", - "requested_threads": 1, - "effective_threads": 1, - "system_info": { - "Python": "3.12.3", - "OS": "Linux", - "OS Version": "#12~24.04.1-Ubuntu SMP Fri Mar 27 23:35:04 UTC 2026", - "Machine": "x86_64", - "CPU": { - "brand_raw": "AMD EPYC 9B45", - "arch": "X86_64", - "hz_advertised_raw": "Unknown", - "count": 16 - }, - "torchvision": "0.26.0+cpu" - }, - "benchmark_results": { - "images_per_second_mean": 863.7557853158189, - "images_per_second_std": 0.8720659298791983, - "images_per_second_p50": 863.2845451285694, - "images_per_second_p90": 864.848920804951, - "images_per_second_p99": 865.1280689253334, - "us_per_image_mean": 1157.7358273360005, - "us_per_image_p50": 1158.366619260014, - "us_per_image_p90": 1158.7838009159968, - "us_per_image_p99": 1158.7937723255934, - "raw_times_s": [ - 57.93835909500012, - 57.84468345200003, - 57.939744012999654, - 57.9183309630007, - 57.792839310999625 - ], - "raw_throughput_ips": [ - 862.9861249266002, - 864.383673937647, - 862.9654972031244, - 863.2845451285694, - 865.1590853831536 - ], - "num_images_total": 50000, - "num_images_decoded": 50000, - "num_images_skipped": 0, - "skip_rate": 0.0, - "skip_indices": [], - "skip_examples": [] - }, - "num_images": 50000, - "num_runs": 5 -} \ No newline at end of file diff --git a/output/linux_AMD-EPYC-9B45/torchvision_dataloader_results.json b/output/linux_AMD-EPYC-9B45/torchvision_dataloader_results.json deleted file mode 100644 index c142c44..0000000 --- a/output/linux_AMD-EPYC-9B45/torchvision_dataloader_results.json +++ /dev/null @@ -1,121 +0,0 @@ -{ - "library": "torchvision", - "benchmark_type": "dataloader", - "batch_size": 32, - "num_threads": 8, - "system_info": { - "Python": "3.12.3", - "OS": "Linux", - "OS Version": "#12~24.04.1-Ubuntu SMP Fri Mar 27 23:35:04 UTC 2026", - "Machine": "x86_64", - "CPU": { - "brand_raw": "AMD EPYC 9B45", - "arch": "X86_64", - "hz_advertised_raw": "Unknown", - "count": 16 - }, - "torchvision": "0.26.0+cpu" - }, - "worker_results": [ - { - "num_workers": 0, - "num_warmup": 1, - "images_per_second_mean": 861.7132008761022, - "images_per_second_std": 1.3740025806587959, - "images_per_second_p50": 862.4593131599916, - "images_per_second_p90": 862.8070229353044, - "images_per_second_p99": 862.8852576347498, - "us_per_image_mean": 1160.4818680733297, - "us_per_image_p50": 1159.4749859399963, - "us_per_image_p90": 1162.3587224519797, - "us_per_image_p99": 1163.007563167176, - "raw_times_s": [ - 58.15398282899878, - 57.94454808500086, - 57.97374929699981 - ], - "raw_throughput_ips": [ - 859.7863390891819, - 862.8939503791327, - 862.4593131599916 - ] - }, - { - "num_workers": 2, - "num_warmup": 1, - "images_per_second_mean": 1532.4067067843328, - "images_per_second_std": 1.7134651264491936, - "images_per_second_p50": 1531.661807117611, - "images_per_second_p90": 1534.1532423453484, - "images_per_second_p99": 1534.7138152715893, - "us_per_image_mean": 652.5690895533262, - "us_per_image_p50": 652.8856405199986, - "us_per_image_p90": 653.1857615760018, - "us_per_image_p99": 653.2532888136026, - "raw_times_s": [ - 32.64428202599993, - 32.66303959200013, - 32.57804181499887 - ], - "raw_throughput_ips": [ - 1531.661807117611, - 1530.782212083105, - 1534.7761011522828 - ] - }, - { - "num_workers": 4, - "num_warmup": 1, - "images_per_second_mean": 2605.452884898472, - "images_per_second_std": 3.1715462823604623, - "images_per_second_p50": 2604.6452379424118, - "images_per_second_p90": 2608.671088406695, - "images_per_second_p99": 2609.576904761159, - "us_per_image_mean": 383.8110014933288, - "us_per_image_p50": 383.92944475999684, - "us_per_image_p90": 384.2374542960024, - "us_per_image_p99": 384.3067564416036, - "raw_times_s": [ - 19.196472237999842, - 19.21572283400019, - 19.159455151999282 - ], - "raw_throughput_ips": [ - 2604.6452379424118, - 2602.0358657302386, - 2609.677551022766 - ] - }, - { - "num_workers": 8, - "num_warmup": 1, - "images_per_second_mean": 2919.755978613895, - "images_per_second_std": 2.4711762690172763, - "images_per_second_p50": 2918.1146728111653, - "images_per_second_p90": 2922.2218503545455, - "images_per_second_p99": 2923.1459653018064, - "us_per_image_mean": 342.49462051999825, - "us_per_image_p50": 342.68701271998, - "us_per_image_p90": 342.70674824001617, - "us_per_image_p99": 342.7111887320243, - "raw_times_s": [ - 17.134350635999, - 17.104258335999475, - 17.13558410600126 - ], - "raw_throughput_ips": [ - 2918.1146728111653, - 2923.248644740391, - 2917.9046182901284 - ] - } - ], - "num_images": 50000, - "num_images_total": 50000, - "num_images_decoded": 50000, - "num_images_skipped": 0, - "skip_rate": 0.0, - "skip_indices": [], - "skip_examples": [], - "num_runs": 3 -} \ No newline at end of file diff --git a/output/linux_AMD-EPYC-9B45/torchvision_default_results.json b/output/linux_AMD-EPYC-9B45/torchvision_default_results.json deleted file mode 100644 index 93b707f..0000000 --- a/output/linux_AMD-EPYC-9B45/torchvision_default_results.json +++ /dev/null @@ -1,53 +0,0 @@ -{ - "library": "torchvision", - "mode": "memory", - "run_tag": "default", - "requested_threads": 0, - "effective_threads": 8, - "system_info": { - "Python": "3.12.3", - "OS": "Linux", - "OS Version": "#12~24.04.1-Ubuntu SMP Fri Mar 27 23:35:04 UTC 2026", - "Machine": "x86_64", - "CPU": { - "brand_raw": "AMD EPYC 9B45", - "arch": "X86_64", - "hz_advertised_raw": "Unknown", - "count": 16 - }, - "torchvision": "0.26.0+cpu" - }, - "benchmark_results": { - "images_per_second_mean": 862.5291251924225, - "images_per_second_std": 1.5854445860525863, - "images_per_second_p50": 861.8019568641548, - "images_per_second_p90": 864.4456385912289, - "images_per_second_p99": 864.7045477219382, - "us_per_image_mean": 1159.3850551239884, - "us_per_image_p50": 1160.3593981599988, - "us_per_image_p90": 1161.5221312879876, - "us_per_image_p99": 1162.0491179287842, - "raw_times_s": [ - 57.86942440799976, - 58.03219101099967, - 58.105383599999186, - 58.01796990799994, - 57.82129485399855 - ], - "raw_throughput_ips": [ - 864.0141233733801, - 861.5907676227973, - 860.5054626986527, - 861.8019568641548, - 864.7333154031281 - ], - "num_images_total": 50000, - "num_images_decoded": 50000, - "num_images_skipped": 0, - "skip_rate": 0.0, - "skip_indices": [], - "skip_examples": [] - }, - "num_images": 50000, - "num_runs": 5 -} \ No newline at end of file diff --git a/output/linux_AMD-EPYC-9B45/turbojpeg_1t_results.json b/output/linux_AMD-EPYC-9B45/turbojpeg_1t_results.json deleted file mode 100644 index a5e9595..0000000 --- a/output/linux_AMD-EPYC-9B45/turbojpeg_1t_results.json +++ /dev/null @@ -1,57 +0,0 @@ -{ - "library": "turbojpeg", - "mode": "memory", - "run_tag": "1t", - "requested_threads": 1, - "effective_threads": 1, - "system_info": { - "Python": "3.12.3", - "OS": "Linux", - "OS Version": "#12~24.04.1-Ubuntu SMP Fri Mar 27 23:35:04 UTC 2026", - "Machine": "x86_64", - "CPU": { - "brand_raw": "AMD EPYC 9B45", - "arch": "X86_64", - "hz_advertised_raw": "Unknown", - "count": 16 - }, - "turbojpeg": "1.8.3" - }, - "benchmark_results": { - "images_per_second_mean": 817.6193806838759, - "images_per_second_std": 16.677388478667233, - "images_per_second_p50": 812.439688032238, - "images_per_second_p90": 837.2413118882355, - "images_per_second_p99": 847.0512951107146, - "us_per_image_mean": 1223.5625395507877, - "us_per_image_p50": 1230.8605976919232, - "us_per_image_p90": 1245.6221141862836, - "us_per_image_p99": 1248.3742851841025, - "raw_times_s": [ - 58.95126248199995, - 60.90818306000074, - 62.050517091000074, - 61.541799023998465, - 62.43275541799994 - ], - "raw_throughput_ips": [ - 848.1412932465456, - 820.8913398507704, - 805.7789418043698, - 812.439688032238, - 800.8456404854562 - ], - "num_images_total": 50000, - "num_images_decoded": 49999, - "num_images_skipped": 1, - "skip_rate": 2e-05, - "skip_indices": [ - 19876 - ], - "skip_examples": [ - "idx=19876: OSError: Unsupported color conversion request" - ] - }, - "num_images": 50000, - "num_runs": 5 -} \ No newline at end of file diff --git a/output/linux_AMD-EPYC-9B45/turbojpeg_dataloader_results.json b/output/linux_AMD-EPYC-9B45/turbojpeg_dataloader_results.json deleted file mode 100644 index 40d1100..0000000 --- a/output/linux_AMD-EPYC-9B45/turbojpeg_dataloader_results.json +++ /dev/null @@ -1,125 +0,0 @@ -{ - "library": "turbojpeg", - "benchmark_type": "dataloader", - "batch_size": 32, - "num_threads": 1, - "system_info": { - "Python": "3.12.3", - "OS": "Linux", - "OS Version": "#12~24.04.1-Ubuntu SMP Fri Mar 27 23:35:04 UTC 2026", - "Machine": "x86_64", - "CPU": { - "brand_raw": "AMD EPYC 9B45", - "arch": "X86_64", - "hz_advertised_raw": "Unknown", - "count": 16 - }, - "turbojpeg": "1.8.3" - }, - "worker_results": [ - { - "num_workers": 0, - "num_warmup": 1, - "images_per_second_mean": 810.8338045333917, - "images_per_second_std": 29.792603326674964, - "images_per_second_p50": 807.968939785197, - "images_per_second_p90": 840.5298897557457, - "images_per_second_p99": 847.8561034991192, - "us_per_image_mean": 1234.9579647126425, - "us_per_image_p50": 1237.6713395268084, - "us_per_image_p90": 1278.6450100881991, - "us_per_image_p99": 1287.864085964512, - "raw_times_s": [ - 64.44313249799961, - 61.882329305000894, - 58.91452803000175 - ], - "raw_throughput_ips": [ - 775.8623465665953, - 807.968939785197, - 848.6701272483829 - ] - }, - { - "num_workers": 2, - "num_warmup": 1, - "images_per_second_mean": 1472.3458400385573, - "images_per_second_std": 4.114982718195194, - "images_per_second_p50": 1470.5287398849925, - "images_per_second_p90": 1476.5395172454298, - "images_per_second_p99": 1477.8919421515284, - "us_per_image_mean": 679.1935505176716, - "us_per_image_p50": 680.0275117902205, - "us_per_image_p90": 680.7914832456503, - "us_per_image_p99": 680.963376823122, - "raw_times_s": [ - 33.82785661200069, - 34.00069556199924, - 34.04844282299928 - ], - "raw_throughput_ips": [ - 1478.0422115855392, - 1470.5287398849925, - 1468.4665686451401 - ] - }, - { - "num_workers": 4, - "num_warmup": 1, - "images_per_second_mean": 2677.9789805766304, - "images_per_second_std": 10.025996713043003, - "images_per_second_p50": 2678.6202260675177, - "images_per_second_p90": 2687.6641106982806, - "images_per_second_p99": 2689.6989847402024, - "us_per_image_mean": 373.4211616699027, - "us_per_image_p50": 373.32653217066905, - "us_per_image_p90": 374.808816248312, - "us_per_image_p99": 375.14233016578163, - "raw_times_s": [ - 18.58750652100025, - 18.758594183998866, - 18.665953282001283 - ], - "raw_throughput_ips": [ - 2689.9250818559713, - 2665.3916338064014, - 2678.6202260675177 - ] - }, - { - "num_workers": 8, - "num_warmup": 1, - "images_per_second_mean": 2799.7605304428544, - "images_per_second_std": 59.3534211893233, - "images_per_second_p50": 2838.2222426329604, - "images_per_second_p90": 2843.7581278035673, - "images_per_second_p99": 2845.0037019669535, - "us_per_image_mean": 357.33641694167926, - "us_per_image_p50": 352.33322640453997, - "us_per_image_p90": 365.02642986860315, - "us_per_image_p99": 367.8824006480173, - "raw_times_s": [ - 18.40961833700021, - 17.57346320800025, - 17.616308987000593 - ], - "raw_throughput_ips": [ - 2715.917249599384, - 2845.1420990962188, - 2838.2222426329604 - ] - } - ], - "num_images": 49999, - "num_images_total": 50000, - "num_images_decoded": 49999, - "num_images_skipped": 1, - "skip_rate": 2e-05, - "skip_indices": [ - 19876 - ], - "skip_examples": [ - "idx=19876: OSError: Unsupported color conversion request" - ], - "num_runs": 3 -} \ No newline at end of file diff --git a/output/linux_INTEL(R)-XEON(R)-PLATINUM-8581C-CPU-@-2.30GHz/ajpegli_1t_results.json b/output/linux_INTEL(R)-XEON(R)-PLATINUM-8581C-CPU-@-2.30GHz/ajpegli_1t_results.json deleted file mode 100644 index c9164b2..0000000 --- a/output/linux_INTEL(R)-XEON(R)-PLATINUM-8581C-CPU-@-2.30GHz/ajpegli_1t_results.json +++ /dev/null @@ -1,57 +0,0 @@ -{ - "library": "ajpegli", - "mode": "memory", - "run_tag": "1t", - "requested_threads": 1, - "effective_threads": 1, - "system_info": { - "Python": "3.12.3", - "OS": "Linux", - "OS Version": "#17~24.04.1-Ubuntu SMP Thu May 7 15:12:38 UTC 2026", - "Machine": "x86_64", - "CPU": { - "brand_raw": "INTEL(R) XEON(R) PLATINUM 8581C CPU @ 2.30GHz", - "arch": "X86_64", - "hz_advertised_raw": "Unknown", - "count": 16 - }, - "ajpegli": "1.0.0" - }, - "benchmark_results": { - "images_per_second_mean": 322.64026591760773, - "images_per_second_std": 0.18115753668035064, - "images_per_second_p50": 322.5440754147659, - "images_per_second_p90": 322.8610102027509, - "images_per_second_p99": 322.8680490550159, - "us_per_image_mean": 3099.4281273665474, - "us_per_image_p50": 3100.351475109502, - "us_per_image_p90": 3101.1020993979873, - "us_per_image_p99": 3101.2256165579306, - "raw_times_s": [ - 155.04171097699998, - 155.05886579499997, - 154.867931527, - 154.85855299800005, - 155.014473404 - ], - "raw_throughput_ips": [ - 322.48741119360596, - 322.45173304764535, - 322.8492787823092, - 322.868831149712, - 322.5440754147659 - ], - "num_images_total": 50000, - "num_images_decoded": 49999, - "num_images_skipped": 1, - "skip_rate": 2e-05, - "skip_indices": [ - 19876 - ], - "skip_examples": [ - "idx=19876: DecodeError: jpegli decode failed: ./lib/jpegli/color_transform.cc:549: Unsupported color transform 4 -> 2" - ] - }, - "num_images": 50000, - "num_runs": 5 -} \ No newline at end of file diff --git a/output/linux_INTEL(R)-XEON(R)-PLATINUM-8581C-CPU-@-2.30GHz/ajpegli_dataloader_results.json b/output/linux_INTEL(R)-XEON(R)-PLATINUM-8581C-CPU-@-2.30GHz/ajpegli_dataloader_results.json deleted file mode 100644 index c04e3f4..0000000 --- a/output/linux_INTEL(R)-XEON(R)-PLATINUM-8581C-CPU-@-2.30GHz/ajpegli_dataloader_results.json +++ /dev/null @@ -1,125 +0,0 @@ -{ - "library": "ajpegli", - "benchmark_type": "dataloader", - "batch_size": 32, - "num_threads": 1, - "system_info": { - "Python": "3.12.3", - "OS": "Linux", - "OS Version": "#17~24.04.1-Ubuntu SMP Thu May 7 15:12:38 UTC 2026", - "Machine": "x86_64", - "CPU": { - "brand_raw": "INTEL(R) XEON(R) PLATINUM 8581C CPU @ 2.30GHz", - "arch": "X86_64", - "hz_advertised_raw": "Unknown", - "count": 16 - }, - "ajpegli": "1.0.0" - }, - "worker_results": [ - { - "num_workers": 0, - "num_warmup": 1, - "images_per_second_mean": 316.0411979979685, - "images_per_second_std": 0.6903132920984998, - "images_per_second_p50": 316.4459478420736, - "images_per_second_p90": 316.5757448806198, - "images_per_second_p99": 316.60494921429273, - "us_per_image_mean": 3164.159560164537, - "us_per_image_p50": 3160.0973462269226, - "us_per_image_p90": 3171.142176611533, - "us_per_image_p99": 3173.62726344807, - "raw_times_s": [ - 157.9207390250001, - 158.69199530700007, - 158.0017072139999 - ], - "raw_throughput_ips": [ - 316.6081941402564, - 315.0694520115754, - 316.4459478420736 - ] - }, - { - "num_workers": 2, - "num_warmup": 1, - "images_per_second_mean": 564.2642895922646, - "images_per_second_std": 0.7479586645605707, - "images_per_second_p50": 564.1352699319291, - "images_per_second_p90": 565.0174685049394, - "images_per_second_p99": 565.2159631838667, - "us_per_image_mean": 1772.222298899311, - "us_per_image_p50": 1772.6244985899643, - "us_per_image_p90": 1774.425853481073, - "us_per_image_p99": 1774.8311583315724, - "raw_times_s": [ - 88.7420347340003, - 88.62945230499963, - 88.45654112900002 - ], - "raw_throughput_ips": [ - 563.4195806966726, - 564.1352699319291, - 565.2380181481919 - ] - }, - { - "num_workers": 4, - "num_warmup": 1, - "images_per_second_mean": 1051.6507789544892, - "images_per_second_std": 1.4857629378390995, - "images_per_second_p50": 1052.559594375203, - "images_per_second_p90": 1052.7815470212427, - "images_per_second_p99": 1052.8314863666014, - "us_per_image_mean": 950.8878973312795, - "us_per_image_p50": 950.0649705194105, - "us_per_image_p90": 952.2402817376299, - "us_per_image_p99": 952.7297267617291, - "raw_times_s": [ - 47.50229846100001, - 47.48978078200025, - 47.638252692999686 - ], - "raw_throughput_ips": [ - 1052.559594375203, - 1052.8370351827525, - 1049.555707305512 - ] - }, - { - "num_workers": 8, - "num_warmup": 1, - "images_per_second_mean": 1497.8681551051213, - "images_per_second_std": 15.895543024374971, - "images_per_second_p50": 1507.2954898289122, - "images_per_second_p90": 1510.1213489640195, - "images_per_second_p99": 1510.7571672694187, - "us_per_image_mean": 667.6912427981895, - "us_per_image_p50": 663.4399205384118, - "us_per_image_p90": 674.8839949718999, - "us_per_image_p99": 677.4589117194347, - "raw_times_s": [ - 33.886572934000014, - 33.09377782499996, - 33.17133258700005 - ], - "raw_throughput_ips": [ - 1475.481161738655, - 1510.8278137477964, - 1507.2954898289122 - ] - } - ], - "num_images": 49999, - "num_images_total": 50000, - "num_images_decoded": 49999, - "num_images_skipped": 1, - "skip_rate": 2e-05, - "skip_indices": [ - 19876 - ], - "skip_examples": [ - "idx=19876: DecodeError: jpegli decode failed: ./lib/jpegli/color_transform.cc:549: Unsupported color transform 4 -> 2" - ], - "num_runs": 3 -} \ No newline at end of file diff --git a/output/linux_INTEL(R)-XEON(R)-PLATINUM-8581C-CPU-@-2.30GHz/imagecodecs_1t_results.json b/output/linux_INTEL(R)-XEON(R)-PLATINUM-8581C-CPU-@-2.30GHz/imagecodecs_1t_results.json deleted file mode 100644 index 4fd2b16..0000000 --- a/output/linux_INTEL(R)-XEON(R)-PLATINUM-8581C-CPU-@-2.30GHz/imagecodecs_1t_results.json +++ /dev/null @@ -1,53 +0,0 @@ -{ - "library": "imagecodecs", - "mode": "memory", - "run_tag": "1t", - "requested_threads": 1, - "effective_threads": 1, - "system_info": { - "Python": "3.12.3", - "OS": "Linux", - "OS Version": "#12~24.04.1-Ubuntu SMP Fri Mar 27 23:35:04 UTC 2026", - "Machine": "x86_64", - "CPU": { - "brand_raw": "INTEL(R) XEON(R) PLATINUM 8581C CPU @ 2.30GHz", - "arch": "X86_64", - "hz_advertised_raw": "Unknown", - "count": 16 - }, - "imagecodecs": "2026.3.6" - }, - "benchmark_results": { - "images_per_second_mean": 723.4317318449605, - "images_per_second_std": 0.15811248268794392, - "images_per_second_p50": 723.3406315441213, - "images_per_second_p90": 723.62613073312, - "images_per_second_p99": 723.6471911072899, - "us_per_image_mean": 1382.300504312, - "us_per_image_p50": 1382.4745305200008, - "us_per_image_p90": 1382.58851044, - "us_per_image_p99": 1382.64171448, - "raw_times_s": [ - 69.12372652600004, - 69.09980627099998, - 69.132381302, - 69.09422012700003, - 69.124991852 - ], - "raw_throughput_ips": [ - 723.3406315441213, - 723.5910301095034, - 723.2500755554547, - 723.6495311488644, - 723.3273908668584 - ], - "num_images_total": 50000, - "num_images_decoded": 50000, - "num_images_skipped": 0, - "skip_rate": 0.0, - "skip_indices": [], - "skip_examples": [] - }, - "num_images": 50000, - "num_runs": 5 -} \ No newline at end of file diff --git a/output/linux_INTEL(R)-XEON(R)-PLATINUM-8581C-CPU-@-2.30GHz/imagecodecs_dataloader_results.json b/output/linux_INTEL(R)-XEON(R)-PLATINUM-8581C-CPU-@-2.30GHz/imagecodecs_dataloader_results.json deleted file mode 100644 index 08d0f2d..0000000 --- a/output/linux_INTEL(R)-XEON(R)-PLATINUM-8581C-CPU-@-2.30GHz/imagecodecs_dataloader_results.json +++ /dev/null @@ -1,121 +0,0 @@ -{ - "library": "imagecodecs", - "benchmark_type": "dataloader", - "batch_size": 32, - "num_threads": 1, - "system_info": { - "Python": "3.12.3", - "OS": "Linux", - "OS Version": "#12~24.04.1-Ubuntu SMP Fri Mar 27 23:35:04 UTC 2026", - "Machine": "x86_64", - "CPU": { - "brand_raw": "INTEL(R) XEON(R) PLATINUM 8581C CPU @ 2.30GHz", - "arch": "X86_64", - "hz_advertised_raw": "Unknown", - "count": 16 - }, - "imagecodecs": "2026.3.6" - }, - "worker_results": [ - { - "num_workers": 0, - "num_warmup": 1, - "images_per_second_mean": 719.4772844182186, - "images_per_second_std": 0.444298695399965, - "images_per_second_p50": 719.3597038056746, - "images_per_second_p90": 719.9284326037098, - "images_per_second_p99": 720.0563965832678, - "us_per_image_mean": 1389.898476173333, - "us_per_image_p50": 1390.12512754, - "us_per_image_p90": 1390.6791179559996, - "us_per_image_p99": 1390.8037657995994, - "raw_times_s": [ - 69.43763427099998, - 69.506256377, - 69.54088077799997 - ], - "raw_throughput_ips": [ - 720.0706148032186, - 719.3597038056746, - 719.0015346457627 - ] - }, - { - "num_workers": 2, - "num_warmup": 1, - "images_per_second_mean": 1135.7242982305957, - "images_per_second_std": 2.775589562419265, - "images_per_second_p50": 1135.9884610966942, - "images_per_second_p90": 1138.3848115904875, - "images_per_second_p99": 1138.9239904515912, - "us_per_image_mean": 880.5006428933333, - "us_per_image_p50": 880.2906316799999, - "us_per_image_p90": 882.6467347199978, - "us_per_image_p99": 883.1768579039973, - "raw_times_s": [ - 43.89877682600013, - 44.014531584, - 44.16178802399986 - ], - "raw_throughput_ips": [ - 1138.983899213936, - 1135.9884610966942, - 1132.2005343811566 - ] - }, - { - "num_workers": 4, - "num_warmup": 1, - "images_per_second_mean": 1629.3062982086458, - "images_per_second_std": 17.763885239237798, - "images_per_second_p50": 1622.9309440396667, - "images_per_second_p90": 1647.4165621350892, - "images_per_second_p99": 1652.9258262065593, - "us_per_image_mean": 613.8307166866676, - "us_per_image_p50": 616.1691621399996, - "us_per_image_p90": 619.6811363960014, - "us_per_image_p99": 620.4713306036018, - "raw_times_s": [ - 31.027956498000094, - 30.80845810699998, - 30.23819289800008 - ], - "raw_throughput_ips": [ - 1611.4499839273253, - 1622.9309440396667, - 1653.537966658945 - ] - }, - { - "num_workers": 8, - "num_warmup": 1, - "images_per_second_mean": 1676.7408233000078, - "images_per_second_std": 12.819258002157895, - "images_per_second_p50": 1673.1867627625415, - "images_per_second_p90": 1689.7681687862948, - "images_per_second_p99": 1693.4989851416394, - "us_per_image_mean": 596.4298513800001, - "us_per_image_p50": 597.6619121400017, - "us_per_image_p90": 600.5553681879992, - "us_per_image_p99": 601.2063957987987, - "raw_times_s": [ - 30.063936609999928, - 29.51744549, - 29.883095607000087 - ], - "raw_throughput_ips": [ - 1663.1221868452483, - 1693.9135202922332, - 1673.1867627625415 - ] - } - ], - "num_images": 50000, - "num_images_total": 50000, - "num_images_decoded": 50000, - "num_images_skipped": 0, - "skip_rate": 0.0, - "skip_indices": [], - "skip_examples": [], - "num_runs": 3 -} \ No newline at end of file diff --git a/output/linux_INTEL(R)-XEON(R)-PLATINUM-8581C-CPU-@-2.30GHz/imageio_1t_results.json b/output/linux_INTEL(R)-XEON(R)-PLATINUM-8581C-CPU-@-2.30GHz/imageio_1t_results.json deleted file mode 100644 index 5fab758..0000000 --- a/output/linux_INTEL(R)-XEON(R)-PLATINUM-8581C-CPU-@-2.30GHz/imageio_1t_results.json +++ /dev/null @@ -1,53 +0,0 @@ -{ - "library": "imageio", - "mode": "memory", - "run_tag": "1t", - "requested_threads": 1, - "effective_threads": 1, - "system_info": { - "Python": "3.12.3", - "OS": "Linux", - "OS Version": "#12~24.04.1-Ubuntu SMP Fri Mar 27 23:35:04 UTC 2026", - "Machine": "x86_64", - "CPU": { - "brand_raw": "INTEL(R) XEON(R) PLATINUM 8581C CPU @ 2.30GHz", - "arch": "X86_64", - "hz_advertised_raw": "Unknown", - "count": 16 - }, - "imageio": "2.37.3" - }, - "benchmark_results": { - "images_per_second_mean": 524.2054789963743, - "images_per_second_std": 0.9526258172808227, - "images_per_second_p50": 523.9507120020006, - "images_per_second_p90": 525.3450058397887, - "images_per_second_p99": 525.4911948398986, - "us_per_image_mean": 1907.6551875799996, - "us_per_image_p50": 1908.576469300001, - "us_per_image_p90": 1911.3437620919995, - "us_per_image_p99": 1911.8926391951998, - "raw_times_s": [ - 95.42882346500005, - 95.59768127699999, - 95.21971193800005, - 95.52144834599994, - 95.14613186899987 - ], - "raw_throughput_ips": [ - 523.9507120020006, - 523.0252379774988, - 525.101357506272, - 523.4426494339666, - 525.5074380621331 - ], - "num_images_total": 50000, - "num_images_decoded": 50000, - "num_images_skipped": 0, - "skip_rate": 0.0, - "skip_indices": [], - "skip_examples": [] - }, - "num_images": 50000, - "num_runs": 5 -} \ No newline at end of file diff --git a/output/linux_INTEL(R)-XEON(R)-PLATINUM-8581C-CPU-@-2.30GHz/imageio_dataloader_results.json b/output/linux_INTEL(R)-XEON(R)-PLATINUM-8581C-CPU-@-2.30GHz/imageio_dataloader_results.json deleted file mode 100644 index 8a60570..0000000 --- a/output/linux_INTEL(R)-XEON(R)-PLATINUM-8581C-CPU-@-2.30GHz/imageio_dataloader_results.json +++ /dev/null @@ -1,121 +0,0 @@ -{ - "library": "imageio", - "benchmark_type": "dataloader", - "batch_size": 32, - "num_threads": 1, - "system_info": { - "Python": "3.12.3", - "OS": "Linux", - "OS Version": "#12~24.04.1-Ubuntu SMP Fri Mar 27 23:35:04 UTC 2026", - "Machine": "x86_64", - "CPU": { - "brand_raw": "INTEL(R) XEON(R) PLATINUM 8581C CPU @ 2.30GHz", - "arch": "X86_64", - "hz_advertised_raw": "Unknown", - "count": 16 - }, - "imageio": "2.37.3" - }, - "worker_results": [ - { - "num_workers": 0, - "num_warmup": 1, - "images_per_second_mean": 525.2652166886766, - "images_per_second_std": 5.68055892448565, - "images_per_second_p50": 524.7668453291848, - "images_per_second_p90": 530.9199589332491, - "images_per_second_p99": 532.3044094941636, - "us_per_image_mean": 1904.0225389599952, - "us_per_image_p50": 1905.6081932399957, - "us_per_image_p90": 1923.8239250639945, - "us_per_image_p99": 1927.922464724394, - "raw_times_s": [ - 93.90407828099978, - 95.28040966199978, - 96.41889290099971 - ], - "raw_throughput_ips": [ - 532.4582373342652, - 524.7668453291848, - 518.5705674025799 - ] - }, - { - "num_workers": 2, - "num_warmup": 1, - "images_per_second_mean": 861.10741397923, - "images_per_second_std": 0.1783631098874723, - "images_per_second_p50": 861.0018679183077, - "images_per_second_p90": 861.2872483530664, - "images_per_second_p99": 861.3514589508871, - "us_per_image_mean": 1161.2953583533333, - "us_per_image_p50": 1161.4376661200004, - "us_per_image_p90": 1161.4809284079965, - "us_per_image_p99": 1161.4906624227956, - "raw_times_s": [ - 58.04783324800019, - 58.07188330600002, - 58.07458719899978 - ], - "raw_throughput_ips": [ - 861.3585934617561, - 861.0018679183077, - 860.9617805576266 - ] - }, - { - "num_workers": 4, - "num_warmup": 1, - "images_per_second_mean": 1505.8211623814593, - "images_per_second_std": 3.4770580941924374, - "images_per_second_p50": 1506.3661977752474, - "images_per_second_p90": 1509.0979705888833, - "images_per_second_p99": 1509.7126194719513, - "us_per_image_mean": 664.0930275866685, - "us_per_image_p50": 663.8492031199985, - "us_per_image_p90": 665.6355396960043, - "us_per_image_p99": 666.0374654256057, - "raw_times_s": [ - 33.192460155999925, - 33.30410619200029, - 33.117387790000066 - ], - "raw_throughput_ips": [ - 1506.3661977752474, - 1501.3163755768378, - 1509.7809137922923 - ] - }, - { - "num_workers": 8, - "num_warmup": 1, - "images_per_second_mean": 1642.653001227997, - "images_per_second_std": 16.52270731383043, - "images_per_second_p50": 1632.3844950909843, - "images_per_second_p90": 1659.2486279785946, - "images_per_second_p99": 1665.2930578783069, - "us_per_image_mean": 608.8324706733329, - "us_per_image_p50": 612.6007708399993, - "us_per_image_p90": 613.4352043440012, - "us_per_image_p99": 613.6229518824017, - "raw_times_s": [ - 30.682190636000087, - 30.630038541999966, - 30.012641422999877 - ], - "raw_throughput_ips": [ - 1629.60984739251, - 1632.3844950909843, - 1665.9646612004972 - ] - } - ], - "num_images": 50000, - "num_images_total": 50000, - "num_images_decoded": 50000, - "num_images_skipped": 0, - "skip_rate": 0.0, - "skip_indices": [], - "skip_examples": [], - "num_runs": 3 -} \ No newline at end of file diff --git a/output/linux_INTEL(R)-XEON(R)-PLATINUM-8581C-CPU-@-2.30GHz/jpeg4py_1t_results.json b/output/linux_INTEL(R)-XEON(R)-PLATINUM-8581C-CPU-@-2.30GHz/jpeg4py_1t_results.json deleted file mode 100644 index dd9d1c8..0000000 --- a/output/linux_INTEL(R)-XEON(R)-PLATINUM-8581C-CPU-@-2.30GHz/jpeg4py_1t_results.json +++ /dev/null @@ -1,57 +0,0 @@ -{ - "library": "jpeg4py", - "mode": "memory", - "run_tag": "1t", - "requested_threads": 1, - "effective_threads": 1, - "system_info": { - "Python": "3.12.3", - "OS": "Linux", - "OS Version": "#12~24.04.1-Ubuntu SMP Fri Mar 27 23:35:04 UTC 2026", - "Machine": "x86_64", - "CPU": { - "brand_raw": "INTEL(R) XEON(R) PLATINUM 8581C CPU @ 2.30GHz", - "arch": "X86_64", - "hz_advertised_raw": "Unknown", - "count": 16 - }, - "jpeg4py": "0.1.4" - }, - "benchmark_results": { - "images_per_second_mean": 698.8974357767056, - "images_per_second_std": 0.4717924680501874, - "images_per_second_p50": 699.0733050669916, - "images_per_second_p90": 699.3521670647367, - "images_per_second_p99": 699.4094868186331, - "us_per_image_mean": 1430.8257616712322, - "us_per_image_p50": 1430.4651497229906, - "us_per_image_p90": 1432.0087642352837, - "us_per_image_p99": 1432.3841359667178, - "raw_times_s": [ - 71.61985977099994, - 71.52182702099981, - 71.50307563600018, - 71.4867980099998, - 71.56772585099998 - ], - "raw_throughput_ips": [ - 698.1164185446426, - 699.0733050669916, - 699.2566341415759, - 699.4158556801772, - 698.6249654501407 - ], - "num_images_total": 50000, - "num_images_decoded": 49999, - "num_images_skipped": 1, - "skip_rate": 2e-05, - "skip_indices": [ - 19876 - ], - "skip_examples": [ - "idx=19876: JPEGRuntimeError: tjDecompress2() failed with error -1 and error string Unsupported color conversion request" - ] - }, - "num_images": 50000, - "num_runs": 5 -} \ No newline at end of file diff --git a/output/linux_INTEL(R)-XEON(R)-PLATINUM-8581C-CPU-@-2.30GHz/jpeg4py_dataloader_results.json b/output/linux_INTEL(R)-XEON(R)-PLATINUM-8581C-CPU-@-2.30GHz/jpeg4py_dataloader_results.json deleted file mode 100644 index 3dda949..0000000 --- a/output/linux_INTEL(R)-XEON(R)-PLATINUM-8581C-CPU-@-2.30GHz/jpeg4py_dataloader_results.json +++ /dev/null @@ -1,125 +0,0 @@ -{ - "library": "jpeg4py", - "benchmark_type": "dataloader", - "batch_size": 32, - "num_threads": 1, - "system_info": { - "Python": "3.12.3", - "OS": "Linux", - "OS Version": "#12~24.04.1-Ubuntu SMP Fri Mar 27 23:35:04 UTC 2026", - "Machine": "x86_64", - "CPU": { - "brand_raw": "INTEL(R) XEON(R) PLATINUM 8581C CPU @ 2.30GHz", - "arch": "X86_64", - "hz_advertised_raw": "Unknown", - "count": 16 - }, - "jpeg4py": "0.1.4" - }, - "worker_results": [ - { - "num_workers": 0, - "num_warmup": 1, - "images_per_second_mean": 694.5322886819459, - "images_per_second_std": 0.5241172573468912, - "images_per_second_p50": 694.6345868680363, - "images_per_second_p90": 695.0204426332308, - "images_per_second_p99": 695.1072601803995, - "us_per_image_mean": 1439.8186893404543, - "us_per_image_p50": 1439.6058285965767, - "us_per_image_p90": 1440.9158120602442, - "us_per_image_p99": 1441.2105583395694, - "raw_times_s": [ - 71.92890796799975, - 71.97885182400023, - 72.06072415300014 - ], - "raw_throughput_ips": [ - 695.1169065745294, - 694.6345868680363, - 693.8453726032723 - ] - }, - { - "num_workers": 2, - "num_warmup": 1, - "images_per_second_mean": 1090.7608454306312, - "images_per_second_std": 2.352518054007263, - "images_per_second_p50": 1090.4830133533883, - "images_per_second_p90": 1093.113348072522, - "images_per_second_p99": 1093.7051733843273, - "us_per_image_mean": 916.7955139836143, - "us_per_image_p50": 917.0248300566001, - "us_per_image_p90": 918.6797616192343, - "us_per_image_p99": 919.0521212208271, - "raw_times_s": [ - 45.85032447799995, - 45.95375563200014, - 45.71249660100011 - ], - "raw_throughput_ips": [ - 1090.4830133533883, - 1088.0285911861997, - 1093.7709317523056 - ] - }, - { - "num_workers": 4, - "num_warmup": 1, - "images_per_second_mean": 1650.7759877549222, - "images_per_second_std": 25.67940786599363, - "images_per_second_p50": 1634.6613110196429, - "images_per_second_p90": 1676.546804638799, - "images_per_second_p99": 1685.9710407031089, - "us_per_image_mean": 605.9207721221136, - "us_per_image_p50": 611.747518130368, - "us_per_image_p90": 612.9518698973992, - "us_per_image_p99": 613.2228490449812, - "raw_times_s": [ - 30.586764159000268, - 30.662034639000012, - 29.63749925800039 - ], - "raw_throughput_ips": [ - 1634.6613110196429, - 1630.6484742015355, - 1687.0181780435878 - ] - }, - { - "num_workers": 8, - "num_warmup": 1, - "images_per_second_mean": 1650.863655730828, - "images_per_second_std": 30.485442299800727, - "images_per_second_p50": 1636.3995162058054, - "images_per_second_p90": 1681.8948251838026, - "images_per_second_p99": 1692.131269703852, - "us_per_image_mean": 605.9479020046955, - "us_per_image_p50": 611.0977118342247, - "us_per_image_p90": 615.1573461309123, - "us_per_image_p99": 616.070763847667, - "raw_times_s": [ - 30.5542744939994, - 29.528096399999413, - 30.807996562999506 - ], - "raw_throughput_ips": [ - 1636.3995162058054, - 1693.268652428302, - 1622.9227985583764 - ] - } - ], - "num_images": 49999, - "num_images_total": 50000, - "num_images_decoded": 49999, - "num_images_skipped": 1, - "skip_rate": 2e-05, - "skip_indices": [ - 19876 - ], - "skip_examples": [ - "idx=19876: JPEGRuntimeError: tjDecompress2() failed with error -1 and error string Unsupported color conversion request" - ], - "num_runs": 3 -} \ No newline at end of file diff --git a/output/linux_INTEL(R)-XEON(R)-PLATINUM-8581C-CPU-@-2.30GHz/kornia_1t_results.json b/output/linux_INTEL(R)-XEON(R)-PLATINUM-8581C-CPU-@-2.30GHz/kornia_1t_results.json deleted file mode 100644 index f2aa8b4..0000000 --- a/output/linux_INTEL(R)-XEON(R)-PLATINUM-8581C-CPU-@-2.30GHz/kornia_1t_results.json +++ /dev/null @@ -1,57 +0,0 @@ -{ - "library": "kornia", - "mode": "memory", - "run_tag": "1t", - "requested_threads": 1, - "effective_threads": 1, - "system_info": { - "Python": "3.12.3", - "OS": "Linux", - "OS Version": "#12~24.04.1-Ubuntu SMP Fri Mar 27 23:35:04 UTC 2026", - "Machine": "x86_64", - "CPU": { - "brand_raw": "INTEL(R) XEON(R) PLATINUM 8581C CPU @ 2.30GHz", - "arch": "X86_64", - "hz_advertised_raw": "Unknown", - "count": 16 - }, - "kornia": "0.1.10" - }, - "benchmark_results": { - "images_per_second_mean": 663.6562498250526, - "images_per_second_std": 0.523317539325529, - "images_per_second_p50": 663.6910695229949, - "images_per_second_p90": 664.1193681771842, - "images_per_second_p99": 664.1301779145743, - "us_per_image_mean": 1506.805100606018, - "us_per_image_p50": 1506.725110402217, - "us_per_image_p90": 1508.1146493769847, - "us_per_image_p99": 1508.923450351396, - "raw_times_s": [ - 75.28480294900055, - 75.33474879500045, - 75.33682562100057, - 75.4491568429994, - 75.28820691800047 - ], - "raw_throughput_ips": [ - 664.1313789965066, - 663.6910695229949, - 663.6727734127212, - 662.6846752448392, - 664.1013519482009 - ], - "num_images_total": 50000, - "num_images_decoded": 49999, - "num_images_skipped": 1, - "skip_rate": 2e-05, - "skip_indices": [ - 19876 - ], - "skip_examples": [ - "idx=19876: ValueError: Unsupported number of channels: 4" - ] - }, - "num_images": 50000, - "num_runs": 5 -} \ No newline at end of file diff --git a/output/linux_INTEL(R)-XEON(R)-PLATINUM-8581C-CPU-@-2.30GHz/kornia_dataloader_results.json b/output/linux_INTEL(R)-XEON(R)-PLATINUM-8581C-CPU-@-2.30GHz/kornia_dataloader_results.json deleted file mode 100644 index 5cb65c2..0000000 --- a/output/linux_INTEL(R)-XEON(R)-PLATINUM-8581C-CPU-@-2.30GHz/kornia_dataloader_results.json +++ /dev/null @@ -1,125 +0,0 @@ -{ - "library": "kornia", - "benchmark_type": "dataloader", - "batch_size": 32, - "num_threads": 1, - "system_info": { - "Python": "3.12.3", - "OS": "Linux", - "OS Version": "#12~24.04.1-Ubuntu SMP Fri Mar 27 23:35:04 UTC 2026", - "Machine": "x86_64", - "CPU": { - "brand_raw": "INTEL(R) XEON(R) PLATINUM 8581C CPU @ 2.30GHz", - "arch": "X86_64", - "hz_advertised_raw": "Unknown", - "count": 16 - }, - "kornia": "0.1.10" - }, - "worker_results": [ - { - "num_workers": 0, - "num_warmup": 1, - "images_per_second_mean": 650.739192237102, - "images_per_second_std": 0.2874619678679915, - "images_per_second_p50": 650.9278532658701, - "images_per_second_p90": 650.9509482584039, - "images_per_second_p99": 650.9561446317239, - "us_per_image_mean": 1536.7142583851698, - "us_per_image_p50": 1536.2685664513301, - "us_per_image_p90": 1537.3927316546344, - "us_per_image_p99": 1537.645668825378, - "raw_times_s": [ - 76.88215097400007, - 76.80848558700018, - 76.81189205400005 - ], - "raw_throughput_ips": [ - 650.3330014388985, - 650.9567220065372, - 650.9278532658701 - ] - }, - { - "num_workers": 2, - "num_warmup": 1, - "images_per_second_mean": 977.4006957371654, - "images_per_second_std": 4.076864542055979, - "images_per_second_p50": 976.4384118583705, - "images_per_second_p90": 981.5316187357167, - "images_per_second_p99": 982.6775902831197, - "us_per_image_mean": 1023.1396175523454, - "us_per_image_p50": 1024.130132382632, - "us_per_image_p90": 1027.0602640252787, - "us_per_image_p99": 1027.7195436448742, - "raw_times_s": [ - 50.873778670999855, - 51.20548248899922, - 51.38861205400008 - ], - "raw_throughput_ips": [ - 982.8049204550533, - 976.4384118583705, - 972.9587548980726 - ] - }, - { - "num_workers": 4, - "num_warmup": 1, - "images_per_second_mean": 1287.3118735882697, - "images_per_second_std": 29.248001443583316, - "images_per_second_p50": 1267.7162836893092, - "images_per_second_p90": 1316.4681454494278, - "images_per_second_p99": 1327.4373143454543, - "us_per_image_mean": 777.2074184683721, - "us_per_image_p50": 788.8200324206604, - "us_per_image_p90": 789.8936254925127, - "us_per_image_p99": 790.1351839336795, - "raw_times_s": [ - 39.507311026000025, - 39.4402128010006, - 37.631257320999794 - ], - "raw_throughput_ips": [ - 1265.5632261860426, - 1267.7162836893092, - 1328.6561108894573 - ] - }, - { - "num_workers": 8, - "num_warmup": 1, - "images_per_second_mean": 1421.5827148165306, - "images_per_second_std": 79.17702916944937, - "images_per_second_p50": 1451.5196460743177, - "images_per_second_p90": 1490.348631774074, - "images_per_second_p99": 1499.0851535565191, - "us_per_image_mean": 705.6964960232543, - "us_per_image_p50": 688.9331485829575, - "us_per_image_p90": 746.9982350367055, - "us_per_image_p99": 760.0628794887988, - "raw_times_s": [ - 38.07496381800047, - 34.445968495999296, - 33.33142500000031 - ], - "raw_throughput_ips": [ - 1313.1726201762608, - 1451.5196460743177, - 1500.055878199013 - ] - } - ], - "num_images": 49999, - "num_images_total": 50000, - "num_images_decoded": 49999, - "num_images_skipped": 1, - "skip_rate": 2e-05, - "skip_indices": [ - 19876 - ], - "skip_examples": [ - "idx=19876: ValueError: Unsupported number of channels: 4" - ], - "num_runs": 3 -} \ No newline at end of file diff --git a/output/linux_INTEL(R)-XEON(R)-PLATINUM-8581C-CPU-@-2.30GHz/opencv_1t_results.json b/output/linux_INTEL(R)-XEON(R)-PLATINUM-8581C-CPU-@-2.30GHz/opencv_1t_results.json deleted file mode 100644 index 059c440..0000000 --- a/output/linux_INTEL(R)-XEON(R)-PLATINUM-8581C-CPU-@-2.30GHz/opencv_1t_results.json +++ /dev/null @@ -1,53 +0,0 @@ -{ - "library": "opencv", - "mode": "memory", - "run_tag": "1t", - "requested_threads": 1, - "effective_threads": 1, - "system_info": { - "Python": "3.12.3", - "OS": "Linux", - "OS Version": "#12~24.04.1-Ubuntu SMP Fri Mar 27 23:35:04 UTC 2026", - "Machine": "x86_64", - "CPU": { - "brand_raw": "INTEL(R) XEON(R) PLATINUM 8581C CPU @ 2.30GHz", - "arch": "X86_64", - "hz_advertised_raw": "Unknown", - "count": 16 - }, - "opencv": "4.13.0.92" - }, - "benchmark_results": { - "images_per_second_mean": 720.9958601930637, - "images_per_second_std": 0.5591207304133556, - "images_per_second_p50": 721.3010225263682, - "images_per_second_p90": 721.3419143587097, - "images_per_second_p99": 721.3568536398317, - "us_per_image_mean": 1386.9713506680018, - "us_per_image_p50": 1386.3837271400007, - "us_per_image_p90": 1388.15873981201, - "us_per_image_p99": 1389.00595737521, - "raw_times_s": [ - 69.31918635700004, - 69.4550046330005, - 69.31366173700007, - 69.3373355270005, - 69.31764941299934 - ], - "raw_throughput_ips": [ - 721.3010225263682, - 719.8905286120053, - 721.3585135599564, - 721.1122207101483, - 721.3170155568396 - ], - "num_images_total": 50000, - "num_images_decoded": 50000, - "num_images_skipped": 0, - "skip_rate": 0.0, - "skip_indices": [], - "skip_examples": [] - }, - "num_images": 50000, - "num_runs": 5 -} \ No newline at end of file diff --git a/output/linux_INTEL(R)-XEON(R)-PLATINUM-8581C-CPU-@-2.30GHz/opencv_dataloader_results.json b/output/linux_INTEL(R)-XEON(R)-PLATINUM-8581C-CPU-@-2.30GHz/opencv_dataloader_results.json deleted file mode 100644 index 28a6aea..0000000 --- a/output/linux_INTEL(R)-XEON(R)-PLATINUM-8581C-CPU-@-2.30GHz/opencv_dataloader_results.json +++ /dev/null @@ -1,121 +0,0 @@ -{ - "library": "opencv", - "benchmark_type": "dataloader", - "batch_size": 32, - "num_threads": 16, - "system_info": { - "Python": "3.12.3", - "OS": "Linux", - "OS Version": "#12~24.04.1-Ubuntu SMP Fri Mar 27 23:35:04 UTC 2026", - "Machine": "x86_64", - "CPU": { - "brand_raw": "INTEL(R) XEON(R) PLATINUM 8581C CPU @ 2.30GHz", - "arch": "X86_64", - "hz_advertised_raw": "Unknown", - "count": 16 - }, - "opencv": "4.13.0.92" - }, - "worker_results": [ - { - "num_workers": 0, - "num_warmup": 1, - "images_per_second_mean": 711.6260891781943, - "images_per_second_std": 0.44739300344751537, - "images_per_second_p50": 711.9133825297141, - "images_per_second_p90": 711.9591892026281, - "images_per_second_p99": 711.9694957040336, - "us_per_image_mean": 1405.2329033933286, - "us_per_image_p50": 1404.665264819996, - "us_per_image_p90": 1406.1179705639952, - "us_per_image_p99": 1406.444829356395, - "raw_times_s": [ - 70.32405734999975, - 70.2332632409998, - 70.22761491799974 - ], - "raw_throughput_ips": [ - 710.9942441340122, - 711.9133825297141, - 711.9706408708565 - ] - }, - { - "num_workers": 2, - "num_warmup": 1, - "images_per_second_mean": 1103.0605226337668, - "images_per_second_std": 1.4801724048568716, - "images_per_second_p50": 1102.128345308388, - "images_per_second_p90": 1104.5454863975406, - "images_per_second_p99": 1105.0893431426, - "us_per_image_mean": 906.5702003133387, - "us_per_image_p50": 907.3353428000155, - "us_per_image_p90": 907.4834898080044, - "us_per_image_p99": 907.5168228848019, - "raw_times_s": [ - 45.24273657899994, - 45.366767140000775, - 45.37602632800008 - ], - "raw_throughput_ips": [ - 1105.1497716698289, - 1102.128345308388, - 1101.903450923084 - ] - }, - { - "num_workers": 4, - "num_warmup": 1, - "images_per_second_mean": 1588.4875114632516, - "images_per_second_std": 57.69915095878746, - "images_per_second_p50": 1572.0083650558029, - "images_per_second_p90": 1647.151873799877, - "images_per_second_p99": 1664.0591632672936, - "us_per_image_mean": 630.3496342733342, - "us_per_image_p50": 636.1289304999991, - "us_per_image_p90": 650.9517443880068, - "us_per_image_p99": 654.2868775128085, - "raw_times_s": [ - 32.732872393000434, - 31.806446524999956, - 30.013126222999745 - ], - "raw_throughput_ips": [ - 1527.5164183480565, - 1572.0083650558029, - 1665.9377509858955 - ] - }, - { - "num_workers": 8, - "num_warmup": 1, - "images_per_second_mean": 1707.3317911382483, - "images_per_second_std": 5.003690089479942, - "images_per_second_p50": 1705.030980756296, - "images_per_second_p90": 1712.428163767722, - "images_per_second_p99": 1714.0925299452929, - "us_per_image_mean": 585.7142586066706, - "us_per_image_p50": 586.499606920006, - "us_per_image_p90": 587.1455426960092, - "us_per_image_p99": 587.29087824561, - "raw_times_s": [ - 29.324980346000302, - 29.166807112999777, - 29.3653513320005 - ], - "raw_throughput_ips": [ - 1705.030980756296, - 1714.2774595205785, - 1702.68693313787 - ] - } - ], - "num_images": 50000, - "num_images_total": 50000, - "num_images_decoded": 50000, - "num_images_skipped": 0, - "skip_rate": 0.0, - "skip_indices": [], - "skip_examples": [], - "num_runs": 3 -} \ No newline at end of file diff --git a/output/linux_INTEL(R)-XEON(R)-PLATINUM-8581C-CPU-@-2.30GHz/opencv_default_results.json b/output/linux_INTEL(R)-XEON(R)-PLATINUM-8581C-CPU-@-2.30GHz/opencv_default_results.json deleted file mode 100644 index cf2dbac..0000000 --- a/output/linux_INTEL(R)-XEON(R)-PLATINUM-8581C-CPU-@-2.30GHz/opencv_default_results.json +++ /dev/null @@ -1,53 +0,0 @@ -{ - "library": "opencv", - "mode": "memory", - "run_tag": "default", - "requested_threads": 0, - "effective_threads": 16, - "system_info": { - "Python": "3.12.3", - "OS": "Linux", - "OS Version": "#12~24.04.1-Ubuntu SMP Fri Mar 27 23:35:04 UTC 2026", - "Machine": "x86_64", - "CPU": { - "brand_raw": "INTEL(R) XEON(R) PLATINUM 8581C CPU @ 2.30GHz", - "arch": "X86_64", - "hz_advertised_raw": "Unknown", - "count": 16 - }, - "opencv": "4.13.0.92" - }, - "benchmark_results": { - "images_per_second_mean": 720.3070036384524, - "images_per_second_std": 0.5753017600218032, - "images_per_second_p50": 720.4403669622708, - "images_per_second_p90": 720.9322878475243, - "images_per_second_p99": 721.0319705711617, - "us_per_image_mean": 1388.2978131599994, - "us_per_image_p50": 1388.0399348199899, - "us_per_image_p90": 1389.590551520003, - "us_per_image_p99": 1389.6342360800015, - "raw_times_s": [ - 69.4019967409995, - 69.34398750199944, - 69.37062735500058, - 69.48195449600007, - 69.47588719600026 - ], - "raw_throughput_ips": [ - 720.4403669622708, - 721.0430464293436, - 720.7661499747956, - 719.611305736634, - 719.6741490892183 - ], - "num_images_total": 50000, - "num_images_decoded": 50000, - "num_images_skipped": 0, - "skip_rate": 0.0, - "skip_indices": [], - "skip_examples": [] - }, - "num_images": 50000, - "num_runs": 5 -} \ No newline at end of file diff --git a/output/linux_INTEL(R)-XEON(R)-PLATINUM-8581C-CPU-@-2.30GHz/pillow_1t_results.json b/output/linux_INTEL(R)-XEON(R)-PLATINUM-8581C-CPU-@-2.30GHz/pillow_1t_results.json deleted file mode 100644 index 806efe8..0000000 --- a/output/linux_INTEL(R)-XEON(R)-PLATINUM-8581C-CPU-@-2.30GHz/pillow_1t_results.json +++ /dev/null @@ -1,53 +0,0 @@ -{ - "library": "pillow", - "mode": "memory", - "run_tag": "1t", - "requested_threads": 1, - "effective_threads": 1, - "system_info": { - "Python": "3.12.3", - "OS": "Linux", - "OS Version": "#12~24.04.1-Ubuntu SMP Fri Mar 27 23:35:04 UTC 2026", - "Machine": "x86_64", - "CPU": { - "brand_raw": "INTEL(R) XEON(R) PLATINUM 8581C CPU @ 2.30GHz", - "arch": "X86_64", - "hz_advertised_raw": "Unknown", - "count": 16 - }, - "pillow": "12.2.0" - }, - "benchmark_results": { - "images_per_second_mean": 576.8809091900412, - "images_per_second_std": 4.230023184806731, - "images_per_second_p50": 576.9626232350034, - "images_per_second_p90": 581.4641872781203, - "images_per_second_p99": 583.7583333523493, - "us_per_image_mean": 1733.5529543759985, - "us_per_image_p50": 1733.2145267799933, - "us_per_image_p90": 1746.6147892200097, - "us_per_image_p99": 1750.4839012800155, - "raw_times_s": [ - 87.008313456, - 87.54569013100081, - 86.5590110739995, - 85.61449759399966, - 86.66072633899967 - ], - "raw_throughput_ips": [ - 574.6577311291633, - 571.1303426265926, - 577.6406104877386, - 584.0132384717081, - 576.9626232350034 - ], - "num_images_total": 50000, - "num_images_decoded": 50000, - "num_images_skipped": 0, - "skip_rate": 0.0, - "skip_indices": [], - "skip_examples": [] - }, - "num_images": 50000, - "num_runs": 5 -} \ No newline at end of file diff --git a/output/linux_INTEL(R)-XEON(R)-PLATINUM-8581C-CPU-@-2.30GHz/pillow_dataloader_results.json b/output/linux_INTEL(R)-XEON(R)-PLATINUM-8581C-CPU-@-2.30GHz/pillow_dataloader_results.json deleted file mode 100644 index a362163..0000000 --- a/output/linux_INTEL(R)-XEON(R)-PLATINUM-8581C-CPU-@-2.30GHz/pillow_dataloader_results.json +++ /dev/null @@ -1,121 +0,0 @@ -{ - "library": "pillow", - "benchmark_type": "dataloader", - "batch_size": 32, - "num_threads": 1, - "system_info": { - "Python": "3.12.3", - "OS": "Linux", - "OS Version": "#12~24.04.1-Ubuntu SMP Fri Mar 27 23:35:04 UTC 2026", - "Machine": "x86_64", - "CPU": { - "brand_raw": "INTEL(R) XEON(R) PLATINUM 8581C CPU @ 2.30GHz", - "arch": "X86_64", - "hz_advertised_raw": "Unknown", - "count": 16 - }, - "pillow": "12.2.0" - }, - "worker_results": [ - { - "num_workers": 0, - "num_warmup": 1, - "images_per_second_mean": 552.9697497883814, - "images_per_second_std": 4.985607946880808, - "images_per_second_p50": 556.4436518657005, - "images_per_second_p90": 556.525780733994, - "images_per_second_p99": 556.54425972936, - "us_per_image_mean": 1808.5651480999877, - "us_per_image_p50": 1797.1271603999776, - "us_per_image_p90": 1824.8435307680047, - "us_per_image_p99": 1831.0797141008109, - "raw_times_s": [ - 89.8397830269987, - 91.58863116800057, - 89.85635801999888 - ], - "raw_throughput_ips": [ - 556.5463129510673, - 545.9192845483764, - 556.4436518657005 - ] - }, - { - "num_workers": 2, - "num_warmup": 1, - "images_per_second_mean": 890.3751791385324, - "images_per_second_std": 1.8902702962726539, - "images_per_second_p50": 889.183832999999, - "images_per_second_p90": 892.2714381800764, - "images_per_second_p99": 892.9661493455939, - "us_per_image_mean": 1123.1271086133347, - "us_per_image_p50": 1124.6268351799881, - "us_per_image_p90": 1124.9157726360208, - "us_per_image_p99": 1124.9807835636282, - "raw_times_s": [ - 56.231341758999406, - 55.98832418299935, - 56.24940035000145 - ], - "raw_throughput_ips": [ - 889.183832999999, - 893.0433394750958, - 888.8983649405021 - ] - }, - { - "num_workers": 4, - "num_warmup": 1, - "images_per_second_mean": 1548.8758923922242, - "images_per_second_std": 14.563197529758012, - "images_per_second_p50": 1553.4000991607886, - "images_per_second_p90": 1561.8914820266111, - "images_per_second_p99": 1563.8020431714212, - "us_per_image_mean": 645.6868396199934, - "us_per_image_p50": 643.7491542199859, - "us_per_image_p90": 651.8946570199914, - "us_per_image_p99": 653.7273951499926, - "raw_times_s": [ - 32.69655163599964, - 31.969016596000074, - 32.1874577109993 - ], - "raw_throughput_ips": [ - 1529.213250272817, - 1564.0143277430668, - 1553.4000991607886 - ] - }, - { - "num_workers": 8, - "num_warmup": 1, - "images_per_second_mean": 1564.5281113619646, - "images_per_second_std": 12.57176132198265, - "images_per_second_p50": 1570.4264597488204, - "images_per_second_p90": 1574.9685866430227, - "images_per_second_p99": 1575.9905651942183, - "us_per_image_mean": 639.211836346658, - "us_per_image_p50": 636.7697091399896, - "us_per_image_p90": 644.4659018439852, - "us_per_image_p99": 646.1975452023842, - "raw_times_s": [ - 32.319497500999205, - 31.83848545699948, - 31.72379249400001 - ], - "raw_throughput_ips": [ - 1547.0537559704997, - 1570.4264597488204, - 1576.1041183665734 - ] - } - ], - "num_images": 50000, - "num_images_total": 50000, - "num_images_decoded": 50000, - "num_images_skipped": 0, - "skip_rate": 0.0, - "skip_indices": [], - "skip_examples": [], - "num_runs": 3 -} \ No newline at end of file diff --git a/output/linux_INTEL(R)-XEON(R)-PLATINUM-8581C-CPU-@-2.30GHz/pyvips_1t_results.json b/output/linux_INTEL(R)-XEON(R)-PLATINUM-8581C-CPU-@-2.30GHz/pyvips_1t_results.json deleted file mode 100644 index 6d1be9e..0000000 --- a/output/linux_INTEL(R)-XEON(R)-PLATINUM-8581C-CPU-@-2.30GHz/pyvips_1t_results.json +++ /dev/null @@ -1,53 +0,0 @@ -{ - "library": "pyvips", - "mode": "memory", - "run_tag": "1t", - "requested_threads": 1, - "effective_threads": 1, - "system_info": { - "Python": "3.12.3", - "OS": "Linux", - "OS Version": "#12~24.04.1-Ubuntu SMP Fri Mar 27 23:35:04 UTC 2026", - "Machine": "x86_64", - "CPU": { - "brand_raw": "INTEL(R) XEON(R) PLATINUM 8581C CPU @ 2.30GHz", - "arch": "X86_64", - "hz_advertised_raw": "Unknown", - "count": 16 - }, - "pyvips": "3.1.1" - }, - "benchmark_results": { - "images_per_second_mean": 462.4989270179687, - "images_per_second_std": 0.523771130250562, - "images_per_second_p50": 462.2684262977374, - "images_per_second_p90": 463.1276813152023, - "images_per_second_p99": 463.3912683547597, - "us_per_image_mean": 2162.169948932009, - "us_per_image_p50": 2163.24529886002, - "us_per_image_p90": 2164.369077852003, - "us_per_image_p99": 2164.878074731199, - "raw_times_s": [ - 108.16226494300099, - 108.06409515999985, - 108.24673149699993, - 108.1760374860005, - 107.89335814700098 - ], - "raw_throughput_ips": [ - 462.2684262977374, - 462.68836958260675, - 461.9077112862826, - 462.2095721196176, - 463.42055580359937 - ], - "num_images_total": 50000, - "num_images_decoded": 50000, - "num_images_skipped": 0, - "skip_rate": 0.0, - "skip_indices": [], - "skip_examples": [] - }, - "num_images": 50000, - "num_runs": 5 -} \ No newline at end of file diff --git a/output/linux_INTEL(R)-XEON(R)-PLATINUM-8581C-CPU-@-2.30GHz/pyvips_default_results.json b/output/linux_INTEL(R)-XEON(R)-PLATINUM-8581C-CPU-@-2.30GHz/pyvips_default_results.json deleted file mode 100644 index ec43462..0000000 --- a/output/linux_INTEL(R)-XEON(R)-PLATINUM-8581C-CPU-@-2.30GHz/pyvips_default_results.json +++ /dev/null @@ -1,53 +0,0 @@ -{ - "library": "pyvips", - "mode": "memory", - "run_tag": "default", - "requested_threads": 0, - "effective_threads": 16, - "system_info": { - "Python": "3.12.3", - "OS": "Linux", - "OS Version": "#12~24.04.1-Ubuntu SMP Fri Mar 27 23:35:04 UTC 2026", - "Machine": "x86_64", - "CPU": { - "brand_raw": "INTEL(R) XEON(R) PLATINUM 8581C CPU @ 2.30GHz", - "arch": "X86_64", - "hz_advertised_raw": "Unknown", - "count": 16 - }, - "pyvips": "3.1.1" - }, - "benchmark_results": { - "images_per_second_mean": 423.0644287030794, - "images_per_second_std": 0.3308942889190826, - "images_per_second_p50": 422.96408488396094, - "images_per_second_p90": 423.4571738015256, - "images_per_second_p99": 423.64319141046974, - "us_per_image_mean": 2363.7076139239944, - "us_per_image_p50": 2364.2669336200197, - "us_per_image_p90": 2365.367796691986, - "us_per_image_p99": 2365.4945087431856, - "raw_times_s": [ - 118.16220587899988, - 118.01809103099913, - 118.2578304969993, - 118.21334668100098, - 118.27542939299929 - ], - "raw_throughput_ips": [ - 423.1471444532853, - 423.66386003368575, - 422.80498289091065, - 422.96408488396094, - 422.742071253554 - ], - "num_images_total": 50000, - "num_images_decoded": 50000, - "num_images_skipped": 0, - "skip_rate": 0.0, - "skip_indices": [], - "skip_examples": [] - }, - "num_images": 50000, - "num_runs": 5 -} \ No newline at end of file diff --git a/output/linux_INTEL(R)-XEON(R)-PLATINUM-8581C-CPU-@-2.30GHz/run_summary.json b/output/linux_INTEL(R)-XEON(R)-PLATINUM-8581C-CPU-@-2.30GHz/run_summary.json deleted file mode 100644 index a4ce078..0000000 --- a/output/linux_INTEL(R)-XEON(R)-PLATINUM-8581C-CPU-@-2.30GHz/run_summary.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "timestamp_utc": "2026-05-20T06:48:41.391423+00:00", - "system": "linux_INTEL(R)-XEON(R)-PLATINUM-8581C-CPU-@-2.30GHz", - "mode": "both", - "num_images": 50000, - "num_runs": 5, - "dataloader_runs": 3, - "workers": [ - 0, - 2, - 4, - 8 - ], - "libs_requested": [ - "ajpegli" - ], - "libs_skipped_platform": [], - "libs_run": [ - "ajpegli" - ], - "failures": [], - "exit_status": "ok" -} \ No newline at end of file diff --git a/output/linux_INTEL(R)-XEON(R)-PLATINUM-8581C-CPU-@-2.30GHz/simplejpeg_1t_results.json b/output/linux_INTEL(R)-XEON(R)-PLATINUM-8581C-CPU-@-2.30GHz/simplejpeg_1t_results.json deleted file mode 100644 index f9bcbe3..0000000 --- a/output/linux_INTEL(R)-XEON(R)-PLATINUM-8581C-CPU-@-2.30GHz/simplejpeg_1t_results.json +++ /dev/null @@ -1,53 +0,0 @@ -{ - "library": "simplejpeg", - "mode": "memory", - "run_tag": "1t", - "requested_threads": 1, - "effective_threads": 1, - "system_info": { - "Python": "3.12.3", - "OS": "Linux", - "OS Version": "#12~24.04.1-Ubuntu SMP Fri Mar 27 23:35:04 UTC 2026", - "Machine": "x86_64", - "CPU": { - "brand_raw": "INTEL(R) XEON(R) PLATINUM 8581C CPU @ 2.30GHz", - "arch": "X86_64", - "hz_advertised_raw": "Unknown", - "count": 16 - }, - "simplejpeg": "1.9.0" - }, - "benchmark_results": { - "images_per_second_mean": 735.2311229240917, - "images_per_second_std": 0.7940331553899829, - "images_per_second_p50": 735.2513486167435, - "images_per_second_p90": 735.9599593250144, - "images_per_second_p99": 736.0043758733141, - "us_per_image_mean": 1360.118112995995, - "us_per_image_p50": 1360.0791101999857, - "us_per_image_p90": 1361.733473783992, - "us_per_image_p99": 1362.7058651903994, - "raw_times_s": [ - 68.14069543400001, - 67.94531304799966, - 68.00564107199898, - 68.00395550999929, - 67.93392318500082 - ], - "raw_throughput_ips": [ - 733.7758982578803, - 735.8859317445152, - 735.2331249559719, - 735.2513486167435, - 736.0093110453473 - ], - "num_images_total": 50000, - "num_images_decoded": 50000, - "num_images_skipped": 0, - "skip_rate": 0.0, - "skip_indices": [], - "skip_examples": [] - }, - "num_images": 50000, - "num_runs": 5 -} \ No newline at end of file diff --git a/output/linux_INTEL(R)-XEON(R)-PLATINUM-8581C-CPU-@-2.30GHz/simplejpeg_dataloader_results.json b/output/linux_INTEL(R)-XEON(R)-PLATINUM-8581C-CPU-@-2.30GHz/simplejpeg_dataloader_results.json deleted file mode 100644 index c0eeb78..0000000 --- a/output/linux_INTEL(R)-XEON(R)-PLATINUM-8581C-CPU-@-2.30GHz/simplejpeg_dataloader_results.json +++ /dev/null @@ -1,121 +0,0 @@ -{ - "library": "simplejpeg", - "benchmark_type": "dataloader", - "batch_size": 32, - "num_threads": 1, - "system_info": { - "Python": "3.12.3", - "OS": "Linux", - "OS Version": "#12~24.04.1-Ubuntu SMP Fri Mar 27 23:35:04 UTC 2026", - "Machine": "x86_64", - "CPU": { - "brand_raw": "INTEL(R) XEON(R) PLATINUM 8581C CPU @ 2.30GHz", - "arch": "X86_64", - "hz_advertised_raw": "Unknown", - "count": 16 - }, - "simplejpeg": "1.9.0" - }, - "worker_results": [ - { - "num_workers": 0, - "num_warmup": 1, - "images_per_second_mean": 729.579606364451, - "images_per_second_std": 0.1461125892281859, - "images_per_second_p50": 729.5165452836532, - "images_per_second_p90": 729.7285494913544, - "images_per_second_p99": 729.7762504380872, - "us_per_image_mean": 1370.6524022600086, - "us_per_image_p50": 1370.7708296200144, - "us_per_image_p90": 1370.8848178920161, - "us_per_image_p99": 1370.9104652532164, - "raw_times_s": [ - 68.51365310999972, - 68.53854148100072, - 68.54566574800083 - ], - "raw_throughput_ips": [ - 729.7815505432798, - 729.5165452836532, - 729.44072326642 - ] - }, - { - "num_workers": 2, - "num_warmup": 1, - "images_per_second_mean": 1112.5534742417276, - "images_per_second_std": 4.07224499901475, - "images_per_second_p50": 1110.2891605666612, - "images_per_second_p90": 1116.6749656411816, - "images_per_second_p99": 1118.1117717829486, - "us_per_image_mean": 898.8452137533386, - "us_per_image_p50": 900.6662728199808, - "us_per_image_p90": 901.4389182759987, - "us_per_image_p99": 901.6127635036028, - "raw_times_s": [ - 45.08160398200016, - 45.03331364099904, - 44.71186444000159 - ], - "raw_throughput_ips": [ - 1109.09984524871, - 1110.2891605666612, - 1118.2714169098117 - ] - }, - { - "num_workers": 4, - "num_warmup": 1, - "images_per_second_mean": 1640.7052286088085, - "images_per_second_std": 17.62128356536175, - "images_per_second_p50": 1636.1415312870831, - "images_per_second_p90": 1658.591244985818, - "images_per_second_p99": 1663.6424305680334, - "us_per_image_mean": 609.5640409200132, - "us_per_image_p50": 611.1940690200208, - "us_per_image_p90": 615.5268649240097, - "us_per_image_p99": 616.5017440024072, - "raw_times_s": [ - 30.830503195000347, - 30.55970345100104, - 30.044399492000593 - ], - "raw_throughput_ips": [ - 1621.7704811288415, - 1636.1415312870831, - 1664.2036734105018 - ] - }, - { - "num_workers": 8, - "num_warmup": 1, - "images_per_second_mean": 1753.6911291279027, - "images_per_second_std": 42.68470622179776, - "images_per_second_p50": 1740.7671875280766, - "images_per_second_p90": 1797.128471976602, - "images_per_second_p99": 1809.8097609775202, - "us_per_image_mean": 570.5604506733167, - "us_per_image_p50": 574.4593574399914, - "us_per_image_p90": 582.9779370879842, - "us_per_image_p99": 584.8946175087825, - "raw_times_s": [ - 27.605720628998824, - 28.722967871999572, - 29.255379099999118 - ], - "raw_throughput_ips": [ - 1811.2187930887333, - 1740.7671875280766, - 1709.087406766898 - ] - } - ], - "num_images": 50000, - "num_images_total": 50000, - "num_images_decoded": 50000, - "num_images_skipped": 0, - "skip_rate": 0.0, - "skip_indices": [], - "skip_examples": [], - "num_runs": 3 -} \ No newline at end of file diff --git a/output/linux_INTEL(R)-XEON(R)-PLATINUM-8581C-CPU-@-2.30GHz/skimage_1t_results.json b/output/linux_INTEL(R)-XEON(R)-PLATINUM-8581C-CPU-@-2.30GHz/skimage_1t_results.json deleted file mode 100644 index 27479b7..0000000 --- a/output/linux_INTEL(R)-XEON(R)-PLATINUM-8581C-CPU-@-2.30GHz/skimage_1t_results.json +++ /dev/null @@ -1,53 +0,0 @@ -{ - "library": "skimage", - "mode": "memory", - "run_tag": "1t", - "requested_threads": 1, - "effective_threads": 1, - "system_info": { - "Python": "3.12.3", - "OS": "Linux", - "OS Version": "#12~24.04.1-Ubuntu SMP Fri Mar 27 23:35:04 UTC 2026", - "Machine": "x86_64", - "CPU": { - "brand_raw": "INTEL(R) XEON(R) PLATINUM 8581C CPU @ 2.30GHz", - "arch": "X86_64", - "hz_advertised_raw": "Unknown", - "count": 16 - }, - "skimage": "0.26.0" - }, - "benchmark_results": { - "images_per_second_mean": 525.1518670226274, - "images_per_second_std": 2.575414888139275, - "images_per_second_p50": 523.4985577060744, - "images_per_second_p90": 528.3820915567592, - "images_per_second_p99": 529.4219914583002, - "us_per_image_mean": 1904.256703179999, - "us_per_image_p50": 1910.224938120009, - "us_per_image_p90": 1911.964595543992, - "us_per_image_p99": 1912.1608151423948, - "raw_times_s": [ - 94.42201281500093, - 94.93990706399927, - 95.51124690600045, - 95.58187814399935, - 95.60913086599976 - ], - "raw_throughput_ips": [ - 529.5375358918047, - 526.6489250541908, - 523.4985577060744, - 523.1117129198095, - 522.9626035412571 - ], - "num_images_total": 50000, - "num_images_decoded": 50000, - "num_images_skipped": 0, - "skip_rate": 0.0, - "skip_indices": [], - "skip_examples": [] - }, - "num_images": 50000, - "num_runs": 5 -} \ No newline at end of file diff --git a/output/linux_INTEL(R)-XEON(R)-PLATINUM-8581C-CPU-@-2.30GHz/skimage_dataloader_results.json b/output/linux_INTEL(R)-XEON(R)-PLATINUM-8581C-CPU-@-2.30GHz/skimage_dataloader_results.json deleted file mode 100644 index bb83a0c..0000000 --- a/output/linux_INTEL(R)-XEON(R)-PLATINUM-8581C-CPU-@-2.30GHz/skimage_dataloader_results.json +++ /dev/null @@ -1,121 +0,0 @@ -{ - "library": "skimage", - "benchmark_type": "dataloader", - "batch_size": 32, - "num_threads": 1, - "system_info": { - "Python": "3.12.3", - "OS": "Linux", - "OS Version": "#12~24.04.1-Ubuntu SMP Fri Mar 27 23:35:04 UTC 2026", - "Machine": "x86_64", - "CPU": { - "brand_raw": "INTEL(R) XEON(R) PLATINUM 8581C CPU @ 2.30GHz", - "arch": "X86_64", - "hz_advertised_raw": "Unknown", - "count": 16 - }, - "skimage": "0.26.0" - }, - "worker_results": [ - { - "num_workers": 0, - "num_warmup": 1, - "images_per_second_mean": 510.7371937716748, - "images_per_second_std": 4.661600057515731, - "images_per_second_p50": 508.0066775783875, - "images_per_second_p90": 515.4405255973978, - "images_per_second_p99": 517.1131414016752, - "us_per_image_mean": 1958.1162543333267, - "us_per_image_p50": 1968.478061679998, - "us_per_image_p90": 1971.897749135984, - "us_per_image_p99": 1972.6671788135807, - "raw_times_s": [ - 98.4239030839999, - 98.63763354999901, - 96.65590151600009 - ], - "raw_throughput_ips": [ - 508.0066775783875, - 506.9059161344864, - 517.2989876021504 - ] - }, - { - "num_workers": 2, - "num_warmup": 1, - "images_per_second_mean": 833.2460402388875, - "images_per_second_std": 8.34125339937247, - "images_per_second_p50": 834.9525596851024, - "images_per_second_p90": 840.9914901980002, - "images_per_second_p99": 842.3502495634023, - "us_per_image_mean": 1200.2463585466467, - "us_per_image_p50": 1197.6728358999753, - "us_per_image_p90": 1212.4340410359946, - "us_per_image_p99": 1215.755312191599, - "raw_times_s": [ - 60.80621711599997, - 59.883641794998766, - 59.34709487099826 - ], - "raw_throughput_ips": [ - 822.2843382053359, - 834.9525596851024, - 842.5012228262246 - ] - }, - { - "num_workers": 4, - "num_warmup": 1, - "images_per_second_mean": 1477.0650647133027, - "images_per_second_std": 9.62947460590752, - "images_per_second_p50": 1473.845542791563, - "images_per_second_p90": 1486.8764308872735, - "images_per_second_p99": 1489.8083807088085, - "us_per_image_mean": 677.0469356066557, - "us_per_image_p50": 678.4971497799779, - "us_per_image_p90": 680.9499407719923, - "us_per_image_p99": 681.5018187451955, - "raw_times_s": [ - 33.924857488998896, - 34.07815692599979, - 33.55402592599967 - ], - "raw_throughput_ips": [ - 1473.845542791563, - 1467.215498437144, - 1490.1341529112012 - ] - }, - { - "num_workers": 8, - "num_warmup": 1, - "images_per_second_mean": 1614.9556749243839, - "images_per_second_std": 32.15103273905045, - "images_per_second_p50": 1607.6331465602564, - "images_per_second_p90": 1647.5104367484723, - "images_per_second_p99": 1656.482827040821, - "us_per_image_mean": 619.4559931200032, - "us_per_image_p50": 622.0324594200065, - "us_per_image_p90": 630.8144134840113, - "us_per_image_p99": 632.7903531484124, - "raw_times_s": [ - 30.166280896999524, - 31.101622971000324, - 31.650495100000626 - ], - "raw_throughput_ips": [ - 1657.4797592955263, - 1607.6331465602564, - 1579.7541189173692 - ] - } - ], - "num_images": 50000, - "num_images_total": 50000, - "num_images_decoded": 50000, - "num_images_skipped": 0, - "skip_rate": 0.0, - "skip_indices": [], - "skip_examples": [], - "num_runs": 3 -} \ No newline at end of file diff --git a/output/linux_INTEL(R)-XEON(R)-PLATINUM-8581C-CPU-@-2.30GHz/tensorflow_1t_results.json b/output/linux_INTEL(R)-XEON(R)-PLATINUM-8581C-CPU-@-2.30GHz/tensorflow_1t_results.json deleted file mode 100644 index ff51950..0000000 --- a/output/linux_INTEL(R)-XEON(R)-PLATINUM-8581C-CPU-@-2.30GHz/tensorflow_1t_results.json +++ /dev/null @@ -1,53 +0,0 @@ -{ - "library": "tensorflow", - "mode": "memory", - "run_tag": "1t", - "requested_threads": 1, - "effective_threads": 1, - "system_info": { - "Python": "3.12.3", - "OS": "Linux", - "OS Version": "#12~24.04.1-Ubuntu SMP Fri Mar 27 23:35:04 UTC 2026", - "Machine": "x86_64", - "CPU": { - "brand_raw": "INTEL(R) XEON(R) PLATINUM 8581C CPU @ 2.30GHz", - "arch": "X86_64", - "hz_advertised_raw": "Unknown", - "count": 16 - }, - "tensorflow": "2.21.0" - }, - "benchmark_results": { - "images_per_second_mean": 689.1843365980142, - "images_per_second_std": 0.9765831939217465, - "images_per_second_p50": 689.4940331598922, - "images_per_second_p90": 690.1808845333233, - "images_per_second_p99": 690.4203188055998, - "us_per_image_mean": 1450.9935234199802, - "us_per_image_p50": 1450.3388744599943, - "us_per_image_p90": 1453.4599546359823, - "us_per_image_p99": 1454.1118741215614, - "raw_times_s": [ - 72.51694372299971, - 72.48668783799803, - 72.4168627049985, - 72.61867110800085, - 72.70921548099795 - ], - "raw_throughput_ips": [ - 689.4940331598922, - 689.7818274128625, - 690.4469226136305, - 688.5281600049989, - 687.6707397986869 - ], - "num_images_total": 50000, - "num_images_decoded": 50000, - "num_images_skipped": 0, - "skip_rate": 0.0, - "skip_indices": [], - "skip_examples": [] - }, - "num_images": 50000, - "num_runs": 5 -} \ No newline at end of file diff --git a/output/linux_INTEL(R)-XEON(R)-PLATINUM-8581C-CPU-@-2.30GHz/torchvision_1t_results.json b/output/linux_INTEL(R)-XEON(R)-PLATINUM-8581C-CPU-@-2.30GHz/torchvision_1t_results.json deleted file mode 100644 index cf0b46f..0000000 --- a/output/linux_INTEL(R)-XEON(R)-PLATINUM-8581C-CPU-@-2.30GHz/torchvision_1t_results.json +++ /dev/null @@ -1,53 +0,0 @@ -{ - "library": "torchvision", - "mode": "memory", - "run_tag": "1t", - "requested_threads": 1, - "effective_threads": 1, - "system_info": { - "Python": "3.12.3", - "OS": "Linux", - "OS Version": "#12~24.04.1-Ubuntu SMP Fri Mar 27 23:35:04 UTC 2026", - "Machine": "x86_64", - "CPU": { - "brand_raw": "INTEL(R) XEON(R) PLATINUM 8581C CPU @ 2.30GHz", - "arch": "X86_64", - "hz_advertised_raw": "Unknown", - "count": 16 - }, - "torchvision": "0.26.0+cpu" - }, - "benchmark_results": { - "images_per_second_mean": 712.1848307030398, - "images_per_second_std": 0.45954665015700696, - "images_per_second_p50": 712.3271648742825, - "images_per_second_p90": 712.6609684847413, - "images_per_second_p99": 712.691172621199, - "us_per_image_mean": 1404.1304635000051, - "us_per_image_p50": 1403.8493115399979, - "us_per_image_p90": 1405.2259045960018, - "us_per_image_p99": 1405.3435817775855, - "raw_times_s": [ - 70.25148879800145, - 70.1924655769999, - 70.16454430700105, - 70.15628434199971, - 70.26783285099918 - ], - "raw_throughput_ips": [ - 711.7286886797256, - 712.3271648742825, - 712.6106282573116, - 712.694528636361, - 711.5631430675184 - ], - "num_images_total": 50000, - "num_images_decoded": 50000, - "num_images_skipped": 0, - "skip_rate": 0.0, - "skip_indices": [], - "skip_examples": [] - }, - "num_images": 50000, - "num_runs": 5 -} \ No newline at end of file diff --git a/output/linux_INTEL(R)-XEON(R)-PLATINUM-8581C-CPU-@-2.30GHz/torchvision_dataloader_results.json b/output/linux_INTEL(R)-XEON(R)-PLATINUM-8581C-CPU-@-2.30GHz/torchvision_dataloader_results.json deleted file mode 100644 index 820799b..0000000 --- a/output/linux_INTEL(R)-XEON(R)-PLATINUM-8581C-CPU-@-2.30GHz/torchvision_dataloader_results.json +++ /dev/null @@ -1,121 +0,0 @@ -{ - "library": "torchvision", - "benchmark_type": "dataloader", - "batch_size": 32, - "num_threads": 8, - "system_info": { - "Python": "3.12.3", - "OS": "Linux", - "OS Version": "#12~24.04.1-Ubuntu SMP Fri Mar 27 23:35:04 UTC 2026", - "Machine": "x86_64", - "CPU": { - "brand_raw": "INTEL(R) XEON(R) PLATINUM 8581C CPU @ 2.30GHz", - "arch": "X86_64", - "hz_advertised_raw": "Unknown", - "count": 16 - }, - "torchvision": "0.26.0+cpu" - }, - "worker_results": [ - { - "num_workers": 0, - "num_warmup": 1, - "images_per_second_mean": 708.5986855530554, - "images_per_second_std": 0.6296661748832252, - "images_per_second_p50": 708.7183522819818, - "images_per_second_p90": 709.186100431799, - "images_per_second_p99": 709.2913437655078, - "us_per_image_mean": 1411.2371504600135, - "us_per_image_p50": 1410.9977493599945, - "us_per_image_p90": 1412.502791008017, - "us_per_image_p99": 1412.8414253788221, - "raw_times_s": [ - 70.64395257100114, - 70.49173253000117, - 70.54988746799972 - ], - "raw_throughput_ips": [ - 707.774666907931, - 709.3030374692532, - 708.7183522819818 - ] - }, - { - "num_workers": 2, - "num_warmup": 1, - "images_per_second_mean": 1115.2417173358776, - "images_per_second_std": 3.4391858890572817, - "images_per_second_p50": 1115.0945038594105, - "images_per_second_p90": 1118.6393163708674, - "images_per_second_p99": 1119.4368991859453, - "us_per_image_mean": 896.6751266133482, - "us_per_image_p50": 896.7849779000244, - "us_per_image_p90": 899.3608722360077, - "us_per_image_p99": 899.940448461604, - "raw_times_s": [ - 44.66177780600083, - 45.00024229100018, - 44.83924889500122 - ], - "raw_throughput_ips": [ - 1119.5255194987317, - 1111.1051286494906, - 1115.0945038594105 - ] - }, - { - "num_workers": 4, - "num_warmup": 1, - "images_per_second_mean": 1611.633570453315, - "images_per_second_std": 10.355156656734243, - "images_per_second_p50": 1615.9860701126722, - "images_per_second_p90": 1620.4505292714684, - "images_per_second_p99": 1621.4550325821976, - "us_per_image_mean": 620.514149340003, - "us_per_image_p50": 618.8172153800042, - "us_per_image_p90": 624.5935698920002, - "us_per_image_p99": 625.8932496571994, - "raw_times_s": [ - 30.83437870600028, - 30.94086076900021, - 31.301882925999962 - ], - "raw_throughput_ips": [ - 1621.5666440611676, - 1615.9860701126722, - 1597.3479971861059 - ] - }, - { - "num_workers": 8, - "num_warmup": 1, - "images_per_second_mean": 1609.3307113870044, - "images_per_second_std": 24.355003829947904, - "images_per_second_p50": 1608.6018049137415, - "images_per_second_p90": 1633.334081345729, - "images_per_second_p99": 1638.8988435429262, - "us_per_image_mean": 621.5185869333194, - "us_per_image_p50": 621.6578875799678, - "us_per_image_p90": 630.7013360119963, - "us_per_image_p99": 632.7361119092027, - "raw_times_s": [ - 31.08289437899839, - 30.496783754999342, - 31.648109906000172 - ], - "raw_throughput_ips": [ - 1608.6018049137415, - 1639.517150453726, - 1579.8731787935458 - ] - } - ], - "num_images": 50000, - "num_images_total": 50000, - "num_images_decoded": 50000, - "num_images_skipped": 0, - "skip_rate": 0.0, - "skip_indices": [], - "skip_examples": [], - "num_runs": 3 -} \ No newline at end of file diff --git a/output/linux_INTEL(R)-XEON(R)-PLATINUM-8581C-CPU-@-2.30GHz/torchvision_default_results.json b/output/linux_INTEL(R)-XEON(R)-PLATINUM-8581C-CPU-@-2.30GHz/torchvision_default_results.json deleted file mode 100644 index bd7521f..0000000 --- a/output/linux_INTEL(R)-XEON(R)-PLATINUM-8581C-CPU-@-2.30GHz/torchvision_default_results.json +++ /dev/null @@ -1,53 +0,0 @@ -{ - "library": "torchvision", - "mode": "memory", - "run_tag": "default", - "requested_threads": 0, - "effective_threads": 8, - "system_info": { - "Python": "3.12.3", - "OS": "Linux", - "OS Version": "#12~24.04.1-Ubuntu SMP Fri Mar 27 23:35:04 UTC 2026", - "Machine": "x86_64", - "CPU": { - "brand_raw": "INTEL(R) XEON(R) PLATINUM 8581C CPU @ 2.30GHz", - "arch": "X86_64", - "hz_advertised_raw": "Unknown", - "count": 16 - }, - "torchvision": "0.26.0+cpu" - }, - "benchmark_results": { - "images_per_second_mean": 713.8232046090089, - "images_per_second_std": 0.4207510941055492, - "images_per_second_p50": 713.9709146362654, - "images_per_second_p90": 714.214777464474, - "images_per_second_p99": 714.3260218454147, - "us_per_image_mean": 1400.907593240001, - "us_per_image_p50": 1400.6172793599762, - "us_per_image_p90": 1401.8778201400055, - "us_per_image_p99": 1402.2996439120004, - "raw_times_s": [ - 70.02513074300077, - 70.117325661, - 70.0587390260007, - 70.03086396799881, - 69.99483891199998 - ], - "raw_throughput_ips": [ - 714.0293701629062, - 713.0905169107232, - 713.6868390029636, - 713.9709146362654, - 714.3383823321859 - ], - "num_images_total": 50000, - "num_images_decoded": 50000, - "num_images_skipped": 0, - "skip_rate": 0.0, - "skip_indices": [], - "skip_examples": [] - }, - "num_images": 50000, - "num_runs": 5 -} \ No newline at end of file diff --git a/output/linux_INTEL(R)-XEON(R)-PLATINUM-8581C-CPU-@-2.30GHz/turbojpeg_1t_results.json b/output/linux_INTEL(R)-XEON(R)-PLATINUM-8581C-CPU-@-2.30GHz/turbojpeg_1t_results.json deleted file mode 100644 index 1d09f80..0000000 --- a/output/linux_INTEL(R)-XEON(R)-PLATINUM-8581C-CPU-@-2.30GHz/turbojpeg_1t_results.json +++ /dev/null @@ -1,57 +0,0 @@ -{ - "library": "turbojpeg", - "mode": "memory", - "run_tag": "1t", - "requested_threads": 1, - "effective_threads": 1, - "system_info": { - "Python": "3.12.3", - "OS": "Linux", - "OS Version": "#12~24.04.1-Ubuntu SMP Fri Mar 27 23:35:04 UTC 2026", - "Machine": "x86_64", - "CPU": { - "brand_raw": "INTEL(R) XEON(R) PLATINUM 8581C CPU @ 2.30GHz", - "arch": "X86_64", - "hz_advertised_raw": "Unknown", - "count": 16 - }, - "turbojpeg": "1.8.3" - }, - "benchmark_results": { - "images_per_second_mean": 708.1778601722921, - "images_per_second_std": 0.245449848637837, - "images_per_second_p50": 708.1545837162987, - "images_per_second_p90": 708.46154350762, - "images_per_second_p99": 708.4880506047616, - "us_per_image_mean": 1412.0748139202767, - "us_per_image_p50": 1412.1210580211684, - "us_per_image_p90": 1412.6228775895574, - "us_per_image_p99": 1412.6958305358237, - "raw_times_s": [ - 70.62365196599967, - 70.63378411700069, - 70.57845059899955, - 70.57111564399929, - 70.60464078000041 - ], - "raw_throughput_ips": [ - 707.9639555325036, - 707.8624007624965, - 708.4173650123843, - 708.4909958377773, - 708.1545837162987 - ], - "num_images_total": 50000, - "num_images_decoded": 49999, - "num_images_skipped": 1, - "skip_rate": 2e-05, - "skip_indices": [ - 19876 - ], - "skip_examples": [ - "idx=19876: OSError: Unsupported color conversion request" - ] - }, - "num_images": 50000, - "num_runs": 5 -} \ No newline at end of file diff --git a/output/linux_INTEL(R)-XEON(R)-PLATINUM-8581C-CPU-@-2.30GHz/turbojpeg_dataloader_results.json b/output/linux_INTEL(R)-XEON(R)-PLATINUM-8581C-CPU-@-2.30GHz/turbojpeg_dataloader_results.json deleted file mode 100644 index e530c9b..0000000 --- a/output/linux_INTEL(R)-XEON(R)-PLATINUM-8581C-CPU-@-2.30GHz/turbojpeg_dataloader_results.json +++ /dev/null @@ -1,125 +0,0 @@ -{ - "library": "turbojpeg", - "benchmark_type": "dataloader", - "batch_size": 32, - "num_threads": 1, - "system_info": { - "Python": "3.12.3", - "OS": "Linux", - "OS Version": "#12~24.04.1-Ubuntu SMP Fri Mar 27 23:35:04 UTC 2026", - "Machine": "x86_64", - "CPU": { - "brand_raw": "INTEL(R) XEON(R) PLATINUM 8581C CPU @ 2.30GHz", - "arch": "X86_64", - "hz_advertised_raw": "Unknown", - "count": 16 - }, - "turbojpeg": "1.8.3" - }, - "worker_results": [ - { - "num_workers": 0, - "num_warmup": 1, - "images_per_second_mean": 705.1891717551533, - "images_per_second_std": 0.19159493222311866, - "images_per_second_p50": 705.1827244763425, - "images_per_second_p90": 705.3781319890816, - "images_per_second_p99": 705.4220986794479, - "us_per_image_mean": 1418.059314959628, - "us_per_image_p50": 1418.0721751835088, - "us_per_image_p90": 1418.4341251585038, - "us_per_image_p99": 1418.5155639028776, - "raw_times_s": [ - 70.87764027099911, - 70.90219068700026, - 70.92481210799997 - ], - "raw_throughput_ips": [ - 705.4269838672664, - 705.1827244763425, - 704.957806921851 - ] - }, - { - "num_workers": 2, - "num_warmup": 1, - "images_per_second_mean": 1105.8985733989775, - "images_per_second_std": 9.17905310855732, - "images_per_second_p50": 1106.9411604602653, - "images_per_second_p90": 1114.6546006598394, - "images_per_second_p99": 1116.3901247047434, - "us_per_image_mean": 904.3044445488795, - "us_per_image_p50": 903.3903839877096, - "us_per_image_p90": 911.8248189643591, - "us_per_image_p99": 913.7225668341052, - "raw_times_s": [ - 45.69575745199836, - 45.168615809001494, - 44.77858050799841 - ], - "raw_throughput_ips": [ - 1094.1715990269345, - 1106.9411604602653, - 1116.5829607097328 - ] - }, - { - "num_workers": 4, - "num_warmup": 1, - "images_per_second_mean": 1641.6718688084413, - "images_per_second_std": 9.05310102801549, - "images_per_second_p50": 1646.2407704752204, - "images_per_second_p90": 1649.0442431153867, - "images_per_second_p99": 1649.6750244594243, - "us_per_image_mean": 609.1537146876193, - "us_per_image_p50": 607.4445597112323, - "us_per_image_p90": 612.5787845716508, - "us_per_image_p99": 613.7339851652449, - "raw_times_s": [ - 30.30710602399995, - 30.371620541001903, - 30.69250317699698 - ], - "raw_throughput_ips": [ - 1649.7451112754284, - 1646.2407704752204, - 1629.0297246746757 - ] - }, - { - "num_workers": 8, - "num_warmup": 1, - "images_per_second_mean": 1710.4923975303343, - "images_per_second_std": 19.400474589719728, - "images_per_second_p50": 1715.5085627944325, - "images_per_second_p90": 1728.1772772510096, - "images_per_second_p99": 1731.0277380037396, - "us_per_image_mean": 584.7025154836332, - "us_per_image_p50": 582.9175217703819, - "us_per_image_p90": 591.4668714454056, - "us_per_image_p99": 593.3904751222859, - "raw_times_s": [ - 29.67961683899921, - 29.14529317099732, - 28.878713205001986 - ], - "raw_throughput_ips": [ - 1684.6241739314164, - 1715.5085627944325, - 1731.344455865154 - ] - } - ], - "num_images": 49999, - "num_images_total": 50000, - "num_images_decoded": 49999, - "num_images_skipped": 1, - "skip_rate": 2e-05, - "skip_indices": [ - 19876 - ], - "skip_examples": [ - "idx=19876: OSError: Unsupported color conversion request" - ], - "num_runs": 3 -} \ No newline at end of file diff --git a/output/linux_Neoverse-N1/ajpegli_1t_results.json b/output/linux_Neoverse-N1/ajpegli_1t_results.json deleted file mode 100644 index 546ed0f..0000000 --- a/output/linux_Neoverse-N1/ajpegli_1t_results.json +++ /dev/null @@ -1,57 +0,0 @@ -{ - "library": "ajpegli", - "mode": "memory", - "run_tag": "1t", - "requested_threads": 1, - "effective_threads": 1, - "system_info": { - "Python": "3.12.3", - "OS": "Linux", - "OS Version": "#17~24.04.1-Ubuntu SMP Fri May 8 07:28:18 UTC 2026", - "Machine": "aarch64", - "CPU": { - "brand_raw": "Neoverse-N1", - "arch": "ARM_8", - "hz_advertised_raw": "Unknown", - "count": 16 - }, - "ajpegli": "1.0.0" - }, - "benchmark_results": { - "images_per_second_mean": 273.3220241829019, - "images_per_second_std": 0.12797635904768148, - "images_per_second_p50": 273.2632747988807, - "images_per_second_p90": 273.48103471605333, - "images_per_second_p99": 273.52786375416207, - "us_per_image_mean": 3658.6887652433043, - "us_per_image_p50": 3659.4745515510313, - "us_per_image_p90": 3660.2722502770057, - "us_per_image_p99": 3660.3530267557358, - "raw_times_s": [ - 182.97006810300002, - 183.00322100300002, - 183.014439734, - 182.78960036499984, - 182.87656866199995 - ], - "raw_throughput_ips": [ - 273.2632747988807, - 273.21377036954095, - 273.1970224462638, - 273.53306698061857, - 273.4029863192054 - ], - "num_images_total": 50000, - "num_images_decoded": 49999, - "num_images_skipped": 1, - "skip_rate": 2e-05, - "skip_indices": [ - 19876 - ], - "skip_examples": [ - "idx=19876: DecodeError: jpegli decode failed: ./lib/jpegli/color_transform.cc:549: Unsupported color transform 4 -> 2" - ] - }, - "num_images": 50000, - "num_runs": 5 -} \ No newline at end of file diff --git a/output/linux_Neoverse-N1/ajpegli_dataloader_results.json b/output/linux_Neoverse-N1/ajpegli_dataloader_results.json deleted file mode 100644 index 9031d67..0000000 --- a/output/linux_Neoverse-N1/ajpegli_dataloader_results.json +++ /dev/null @@ -1,125 +0,0 @@ -{ - "library": "ajpegli", - "benchmark_type": "dataloader", - "batch_size": 32, - "num_threads": 1, - "system_info": { - "Python": "3.12.3", - "OS": "Linux", - "OS Version": "#17~24.04.1-Ubuntu SMP Fri May 8 07:28:18 UTC 2026", - "Machine": "aarch64", - "CPU": { - "brand_raw": "Neoverse-N1", - "arch": "ARM_8", - "hz_advertised_raw": "Unknown", - "count": 16 - }, - "ajpegli": "1.0.0" - }, - "worker_results": [ - { - "num_workers": 0, - "num_warmup": 1, - "images_per_second_mean": 268.16233327336806, - "images_per_second_std": 0.05438074389495371, - "images_per_second_p50": 268.16100735263853, - "images_per_second_p90": 268.2158725677621, - "images_per_second_p99": 268.2282172411649, - "us_per_image_mean": 3729.084651513033, - "us_per_image_p50": 3729.102936598737, - "us_per_image_p90": 3729.8218244484906, - "us_per_image_p99": 3729.983574214685, - "raw_times_s": [ - 186.49634731900005, - 186.45141772700026, - 186.4037454270001 - ], - "raw_throughput_ips": [ - 268.09640359592265, - 268.16100735263853, - 268.229588871543 - ] - }, - { - "num_workers": 2, - "num_warmup": 1, - "images_per_second_mean": 495.7600850572162, - "images_per_second_std": 0.37217675871723255, - "images_per_second_p50": 495.57713252151075, - "images_per_second_p90": 496.1385944664174, - "images_per_second_p99": 496.2649234040214, - "us_per_image_mean": 2017.105841030155, - "us_per_image_p50": 2017.8493606272168, - "us_per_image_p90": 2018.347794227883, - "us_per_image_p99": 2018.459941788033, - "raw_times_s": [ - 100.89045018200022, - 100.92160165899986, - 100.7477729960001 - ], - "raw_throughput_ips": [ - 495.57713252151075, - 495.4241626974937, - 496.278959952644 - ] - }, - { - "num_workers": 4, - "num_warmup": 1, - "images_per_second_mean": 963.3546117125278, - "images_per_second_std": 1.4435598236964764, - "images_per_second_p50": 962.3768259681572, - "images_per_second_p90": 964.7917803885301, - "images_per_second_p99": 965.335145133114, - "us_per_image_mean": 1038.0416836070056, - "us_per_image_p50": 1039.0940149602975, - "us_per_image_p90": 1039.1677322586443, - "us_per_image_p99": 1039.1843186507724, - "raw_times_s": [ - 51.95826889299997, - 51.953661653999916, - 51.79120786900012 - ], - "raw_throughput_ips": [ - 962.2914901758028, - 962.3768259681572, - 965.3955189936233 - ] - }, - { - "num_workers": 8, - "num_warmup": 1, - "images_per_second_mean": 1447.7777900748115, - "images_per_second_std": 14.357776807600311, - "images_per_second_p50": 1451.8981853524733, - "images_per_second_p90": 1460.7287118971515, - "images_per_second_p99": 1462.715580369704, - "us_per_image_mean": 690.7819469322732, - "us_per_image_p50": 688.7535297505952, - "us_per_image_p90": 697.7791615912336, - "us_per_image_p99": 699.8099287553772, - "raw_times_s": [ - 34.43698773400001, - 35.00107844200011, - 34.17715351800007 - ], - "raw_throughput_ips": [ - 1451.8981853524733, - 1428.49884133864, - 1462.936343533321 - ] - } - ], - "num_images": 49999, - "num_images_total": 50000, - "num_images_decoded": 49999, - "num_images_skipped": 1, - "skip_rate": 2e-05, - "skip_indices": [ - 19876 - ], - "skip_examples": [ - "idx=19876: DecodeError: jpegli decode failed: ./lib/jpegli/color_transform.cc:549: Unsupported color transform 4 -> 2" - ], - "num_runs": 3 -} \ No newline at end of file diff --git a/output/linux_Neoverse-N1/imagecodecs_1t_results.json b/output/linux_Neoverse-N1/imagecodecs_1t_results.json deleted file mode 100644 index c77a7b7..0000000 --- a/output/linux_Neoverse-N1/imagecodecs_1t_results.json +++ /dev/null @@ -1,53 +0,0 @@ -{ - "library": "imagecodecs", - "mode": "memory", - "run_tag": "1t", - "requested_threads": 1, - "effective_threads": 1, - "system_info": { - "Python": "3.12.3", - "OS": "Linux", - "OS Version": "#12~24.04.1-Ubuntu SMP Fri Mar 27 23:33:21 UTC 2026", - "Machine": "aarch64", - "CPU": { - "brand_raw": "Neoverse-N1", - "arch": "ARM_8", - "hz_advertised_raw": "Unknown", - "count": 16 - }, - "imagecodecs": "2026.3.6" - }, - "benchmark_results": { - "images_per_second_mean": 456.8446040219519, - "images_per_second_std": 0.18590177666125468, - "images_per_second_p50": 456.8507537714156, - "images_per_second_p90": 457.05786255321334, - "images_per_second_p99": 457.1330191977853, - "us_per_image_mean": 2188.92848194, - "us_per_image_p50": 2188.89865398, - "us_per_image_p90": 2189.8807864759997, - "us_per_image_p99": 2189.9374917536, - "raw_times_s": [ - 109.48931388399996, - 109.37535582699996, - 109.44493269899999, - 109.42532845800008, - 109.497189617 - ], - "raw_throughput_ips": [ - 456.66557060512065, - 457.1413699360711, - 456.8507537714156, - 456.93260147892664, - 456.63272431822526 - ], - "num_images_total": 50000, - "num_images_decoded": 50000, - "num_images_skipped": 0, - "skip_rate": 0.0, - "skip_indices": [], - "skip_examples": [] - }, - "num_images": 50000, - "num_runs": 5 -} \ No newline at end of file diff --git a/output/linux_Neoverse-N1/imagecodecs_dataloader_results.json b/output/linux_Neoverse-N1/imagecodecs_dataloader_results.json deleted file mode 100644 index aadc7a8..0000000 --- a/output/linux_Neoverse-N1/imagecodecs_dataloader_results.json +++ /dev/null @@ -1,121 +0,0 @@ -{ - "library": "imagecodecs", - "benchmark_type": "dataloader", - "batch_size": 32, - "num_threads": 1, - "system_info": { - "Python": "3.12.3", - "OS": "Linux", - "OS Version": "#12~24.04.1-Ubuntu SMP Fri Mar 27 23:33:21 UTC 2026", - "Machine": "aarch64", - "CPU": { - "brand_raw": "Neoverse-N1", - "arch": "ARM_8", - "hz_advertised_raw": "Unknown", - "count": 16 - }, - "imagecodecs": "2026.3.6" - }, - "worker_results": [ - { - "num_workers": 0, - "num_warmup": 1, - "images_per_second_mean": 453.13031389620863, - "images_per_second_std": 0.40812516244121827, - "images_per_second_p50": 453.02518721062137, - "images_per_second_p90": 453.5445296765952, - "images_per_second_p99": 453.6613817314393, - "us_per_image_mean": 2206.87246094, - "us_per_image_p50": 2207.38278628, - "us_per_image_p90": 2208.6849007600003, - "us_per_image_p99": 2208.9778765180004, - "raw_times_s": [ - 110.21120835800002, - 110.45052146900002, - 110.369139314 - ], - "raw_throughput_ips": [ - 453.6743652930886, - 452.6913891849159, - 453.02518721062137 - ] - }, - { - "num_workers": 2, - "num_warmup": 1, - "images_per_second_mean": 803.2005615337425, - "images_per_second_std": 1.3838942269941181, - "images_per_second_p50": 803.9642757958859, - "images_per_second_p90": 804.2962545645817, - "images_per_second_p99": 804.3709497875383, - "us_per_image_mean": 1245.0227501333347, - "us_per_image_p50": 1243.8363620200016, - "us_per_image_p90": 1247.197042468002, - "us_per_image_p99": 1247.9531955688021, - "raw_times_s": [ - 62.15973379000002, - 62.19181810100008, - 62.40186062900011 - ], - "raw_throughput_ips": [ - 804.3792492567557, - 803.9642757958859, - 801.258159548586 - ] - }, - { - "num_workers": 4, - "num_warmup": 1, - "images_per_second_mean": 1432.5525497375638, - "images_per_second_std": 11.912637749046864, - "images_per_second_p50": 1432.570635324821, - "images_per_second_p90": 1444.2208794847943, - "images_per_second_p99": 1446.8421844207883, - "us_per_image_mean": 698.1029485266648, - "us_per_image_p50": 698.0458591999968, - "us_per_image_p90": 703.802538271998, - "us_per_image_p99": 705.0977910631982, - "raw_times_s": [ - 35.26208540199991, - 34.90229295999984, - 34.551063916999965 - ], - "raw_throughput_ips": [ - 1417.9535733630835, - 1432.570635324821, - 1447.1334405247876 - ] - }, - { - "num_workers": 8, - "num_warmup": 1, - "images_per_second_mean": 1442.6616913988044, - "images_per_second_std": 16.677387019809107, - "images_per_second_p50": 1448.4589926513756, - "images_per_second_p90": 1457.3413489086292, - "images_per_second_p99": 1459.3398790665112, - "us_per_image_mean": 693.2563682333315, - "us_per_image_p50": 690.3888926599984, - "us_per_image_p90": 701.4722864999976, - "us_per_image_p99": 703.9660501139974, - "raw_times_s": [ - 34.51944463299992, - 34.25685385399993, - 35.21215674799987 - ], - "raw_throughput_ips": [ - 1448.4589926513756, - 1459.5619379729426, - 1419.964143572095 - ] - } - ], - "num_images": 50000, - "num_images_total": 50000, - "num_images_decoded": 50000, - "num_images_skipped": 0, - "skip_rate": 0.0, - "skip_indices": [], - "skip_examples": [], - "num_runs": 3 -} \ No newline at end of file diff --git a/output/linux_Neoverse-N1/imageio_1t_results.json b/output/linux_Neoverse-N1/imageio_1t_results.json deleted file mode 100644 index aeec783..0000000 --- a/output/linux_Neoverse-N1/imageio_1t_results.json +++ /dev/null @@ -1,53 +0,0 @@ -{ - "library": "imageio", - "mode": "memory", - "run_tag": "1t", - "requested_threads": 1, - "effective_threads": 1, - "system_info": { - "Python": "3.12.3", - "OS": "Linux", - "OS Version": "#12~24.04.1-Ubuntu SMP Fri Mar 27 23:33:21 UTC 2026", - "Machine": "aarch64", - "CPU": { - "brand_raw": "Neoverse-N1", - "arch": "ARM_8", - "hz_advertised_raw": "Unknown", - "count": 16 - }, - "imageio": "2.37.3" - }, - "benchmark_results": { - "images_per_second_mean": 335.13476949189953, - "images_per_second_std": 1.0977073253344363, - "images_per_second_p50": 335.8284849168346, - "images_per_second_p90": 336.1410765860875, - "images_per_second_p99": 336.26801504082545, - "us_per_image_mean": 2983.9062788599986, - "us_per_image_p50": 2977.7104829200016, - "us_per_image_p90": 2996.0990605039988, - "us_per_image_p99": 2997.693529894402, - "raw_times_s": [ - 148.6846820820001, - 148.84074825299967, - 149.89353465800014, - 149.67208057599964, - 148.88552414600008 - ], - "raw_throughput_ips": [ - 336.2821193135741, - 335.92951249485753, - 333.5700910254797, - 334.06363970875174, - 335.8284849168346 - ], - "num_images_total": 50000, - "num_images_decoded": 50000, - "num_images_skipped": 0, - "skip_rate": 0.0, - "skip_indices": [], - "skip_examples": [] - }, - "num_images": 50000, - "num_runs": 5 -} \ No newline at end of file diff --git a/output/linux_Neoverse-N1/imageio_dataloader_results.json b/output/linux_Neoverse-N1/imageio_dataloader_results.json deleted file mode 100644 index 276cf0a..0000000 --- a/output/linux_Neoverse-N1/imageio_dataloader_results.json +++ /dev/null @@ -1,121 +0,0 @@ -{ - "library": "imageio", - "benchmark_type": "dataloader", - "batch_size": 32, - "num_threads": 1, - "system_info": { - "Python": "3.12.3", - "OS": "Linux", - "OS Version": "#12~24.04.1-Ubuntu SMP Fri Mar 27 23:33:21 UTC 2026", - "Machine": "aarch64", - "CPU": { - "brand_raw": "Neoverse-N1", - "arch": "ARM_8", - "hz_advertised_raw": "Unknown", - "count": 16 - }, - "imageio": "2.37.3" - }, - "worker_results": [ - { - "num_workers": 0, - "num_warmup": 1, - "images_per_second_mean": 333.85151527443696, - "images_per_second_std": 0.4561071850938023, - "images_per_second_p50": 334.0772587178223, - "images_per_second_p90": 334.22499716831607, - "images_per_second_p99": 334.25823831967716, - "us_per_image_mean": 2995.349196673336, - "us_per_image_p50": 2993.319580739999, - "us_per_image_p90": 2999.513658564001, - "us_per_image_p99": 3000.9073260744017, - "raw_times_s": [ - 149.58329156300033, - 149.66597903699994, - 150.0531089010001 - ], - "raw_throughput_ips": [ - 334.2619317809395, - 334.0772587178223, - 333.2153553245491 - ] - }, - { - "num_workers": 2, - "num_warmup": 1, - "images_per_second_mean": 582.160463674344, - "images_per_second_std": 5.148709496735556, - "images_per_second_p50": 578.5852986063285, - "images_per_second_p90": 587.2702179050434, - "images_per_second_p99": 589.2243247472542, - "us_per_image_mean": 1717.8729944733368, - "us_per_image_p50": 1728.3536280800035, - "us_per_image_p90": 1728.665930768002, - "us_per_image_p99": 1728.7361988728017, - "raw_times_s": [ - 86.43720032200008, - 86.41768140400018, - 84.82606744500026 - ], - "raw_throughput_ips": [ - 578.4546446869815, - 578.5852986063285, - 589.4414477297221 - ] - }, - { - "num_workers": 4, - "num_warmup": 1, - "images_per_second_mean": 1104.6427686063455, - "images_per_second_std": 3.0678195582732095, - "images_per_second_p50": 1102.9194713397808, - "images_per_second_p90": 1107.7459737821985, - "images_per_second_p99": 1108.8319368317423, - "us_per_image_mean": 905.2770063999984, - "us_per_image_p50": 906.6845095999997, - "us_per_image_p90": 907.2526714880041, - "us_per_image_p99": 907.3805079128051, - "raw_times_s": [ - 45.36973559800026, - 45.334225479999986, - 45.08758988199952 - ], - "raw_throughput_ips": [ - 1102.0562350864532, - 1102.9194713397808, - 1108.9525993928028 - ] - }, - { - "num_workers": 8, - "num_warmup": 1, - "images_per_second_mean": 1466.0905585123062, - "images_per_second_std": 17.694942798670006, - "images_per_second_p50": 1477.468478609361, - "images_per_second_p90": 1479.2567091451124, - "images_per_second_p99": 1479.6590610156566, - "us_per_image_mean": 682.1863268000016, - "us_per_image_p50": 676.8333906800035, - "us_per_image_p90": 690.4983944240012, - "us_per_image_p99": 693.5730202664006, - "raw_times_s": [ - 33.841669534000175, - 34.69573226800003, - 33.79054721800003 - ], - "raw_throughput_ips": [ - 1477.468478609361, - 1441.0994301485068, - 1479.7037667790503 - ] - } - ], - "num_images": 50000, - "num_images_total": 50000, - "num_images_decoded": 50000, - "num_images_skipped": 0, - "skip_rate": 0.0, - "skip_indices": [], - "skip_examples": [], - "num_runs": 3 -} \ No newline at end of file diff --git a/output/linux_Neoverse-N1/jpeg4py_1t_results.json b/output/linux_Neoverse-N1/jpeg4py_1t_results.json deleted file mode 100644 index bd3e9a9..0000000 --- a/output/linux_Neoverse-N1/jpeg4py_1t_results.json +++ /dev/null @@ -1,57 +0,0 @@ -{ - "library": "jpeg4py", - "mode": "memory", - "run_tag": "1t", - "requested_threads": 1, - "effective_threads": 1, - "system_info": { - "Python": "3.12.3", - "OS": "Linux", - "OS Version": "#12~24.04.1-Ubuntu SMP Fri Mar 27 23:33:21 UTC 2026", - "Machine": "aarch64", - "CPU": { - "brand_raw": "Neoverse-N1", - "arch": "ARM_8", - "hz_advertised_raw": "Unknown", - "count": 16 - }, - "jpeg4py": "0.1.4" - }, - "benchmark_results": { - "images_per_second_mean": 423.2609342181271, - "images_per_second_std": 0.13165962193357417, - "images_per_second_p50": 423.3150549467722, - "images_per_second_p90": 423.3783194366951, - "images_per_second_p99": 423.4023943659498, - "us_per_image_mean": 2362.6090100442016, - "us_per_image_p50": 2362.3067224144443, - "us_per_image_p90": 2363.4871193823865, - "us_per_image_p99": 2363.879545338902, - "raw_times_s": [ - 118.13929097300024, - 118.10651777500061, - 118.08786341599989, - 118.19379348799976, - 118.11297381399982 - ], - "raw_throughput_ips": [ - 423.2207556707519, - 423.33819455460394, - 423.40506935808924, - 423.0255965604185, - 423.3150549467722 - ], - "num_images_total": 50000, - "num_images_decoded": 49999, - "num_images_skipped": 1, - "skip_rate": 2e-05, - "skip_indices": [ - 19876 - ], - "skip_examples": [ - "idx=19876: JPEGRuntimeError: tjDecompress2() failed with error -1 and error string Unsupported color conversion request" - ] - }, - "num_images": 50000, - "num_runs": 5 -} \ No newline at end of file diff --git a/output/linux_Neoverse-N1/jpeg4py_dataloader_results.json b/output/linux_Neoverse-N1/jpeg4py_dataloader_results.json deleted file mode 100644 index 037d77e..0000000 --- a/output/linux_Neoverse-N1/jpeg4py_dataloader_results.json +++ /dev/null @@ -1,125 +0,0 @@ -{ - "library": "jpeg4py", - "benchmark_type": "dataloader", - "batch_size": 32, - "num_threads": 1, - "system_info": { - "Python": "3.12.3", - "OS": "Linux", - "OS Version": "#12~24.04.1-Ubuntu SMP Fri Mar 27 23:33:21 UTC 2026", - "Machine": "aarch64", - "CPU": { - "brand_raw": "Neoverse-N1", - "arch": "ARM_8", - "hz_advertised_raw": "Unknown", - "count": 16 - }, - "jpeg4py": "0.1.4" - }, - "worker_results": [ - { - "num_workers": 0, - "num_warmup": 1, - "images_per_second_mean": 421.65747192783243, - "images_per_second_std": 0.31000480104558364, - "images_per_second_p50": 421.8181447686163, - "images_per_second_p90": 421.90794537788645, - "images_per_second_p99": 421.9281505149722, - "us_per_image_mean": 2371.5944987499774, - "us_per_image_p50": 2370.689863397268, - "us_per_image_p90": 2373.365548702982, - "us_per_image_p99": 2373.9675778967676, - "raw_times_s": [ - 118.50058808199992, - 118.6993494670005, - 118.53212248 - ], - "raw_throughput_ips": [ - 421.93039553020395, - 421.22387548467714, - 421.8181447686163 - ] - }, - { - "num_workers": 2, - "num_warmup": 1, - "images_per_second_mean": 747.1850782849948, - "images_per_second_std": 0.9276077364711164, - "images_per_second_p50": 747.1512792460754, - "images_per_second_p90": 748.1004026354698, - "images_per_second_p99": 748.3139553980834, - "us_per_image_mean": 1338.3585542710819, - "us_per_image_p50": 1338.4170351807006, - "us_per_image_p90": 1339.9742073161372, - "us_per_image_p99": 1340.3245710466103, - "raw_times_s": [ - 66.81342006900013, - 66.91951334199985, - 67.01683465399947 - ], - "raw_throughput_ips": [ - 748.3376834828183, - 747.1512792460754, - 746.0662721260908 - ] - }, - { - "num_workers": 4, - "num_warmup": 1, - "images_per_second_mean": 1376.697381480262, - "images_per_second_std": 6.019822011584779, - "images_per_second_p50": 1375.899514970874, - "images_per_second_p90": 1382.7291914177877, - "images_per_second_p99": 1384.2658686183433, - "us_per_image_mean": 726.3899224917819, - "us_per_image_p50": 726.797261805248, - "us_per_image_p90": 729.4050699293939, - "us_per_image_p99": 729.9918267573267, - "raw_times_s": [ - 36.33913629300059, - 36.502121040999555, - 36.11505186999966 - ], - "raw_throughput_ips": [ - 1375.899514970874, - 1369.7560189403957, - 1384.4366105295162 - ] - }, - { - "num_workers": 8, - "num_warmup": 1, - "images_per_second_mean": 1410.650175452007, - "images_per_second_std": 31.863587805875703, - "images_per_second_p50": 1409.706312101127, - "images_per_second_p90": 1442.051911411435, - "images_per_second_p99": 1449.3296712562542, - "us_per_image_mean": 709.254574798155, - "us_per_image_p50": 709.3676118322323, - "us_per_image_p90": 724.9188968099231, - "us_per_image_p99": 728.4179359299036, - "raw_times_s": [ - 36.439607095999236, - 34.47878013599984, - 35.46767122399979 - ], - "raw_throughput_ips": [ - 1372.1059030158826, - 1450.138311239012, - 1409.706312101127 - ] - } - ], - "num_images": 49999, - "num_images_total": 50000, - "num_images_decoded": 49999, - "num_images_skipped": 1, - "skip_rate": 2e-05, - "skip_indices": [ - 19876 - ], - "skip_examples": [ - "idx=19876: JPEGRuntimeError: tjDecompress2() failed with error -1 and error string Unsupported color conversion request" - ], - "num_runs": 3 -} \ No newline at end of file diff --git a/output/linux_Neoverse-N1/kornia_1t_results.json b/output/linux_Neoverse-N1/kornia_1t_results.json deleted file mode 100644 index a457b3f..0000000 --- a/output/linux_Neoverse-N1/kornia_1t_results.json +++ /dev/null @@ -1,57 +0,0 @@ -{ - "library": "kornia", - "mode": "memory", - "run_tag": "1t", - "requested_threads": 1, - "effective_threads": 1, - "system_info": { - "Python": "3.12.3", - "OS": "Linux", - "OS Version": "#12~24.04.1-Ubuntu SMP Fri Mar 27 23:33:21 UTC 2026", - "Machine": "aarch64", - "CPU": { - "brand_raw": "Neoverse-N1", - "arch": "ARM_8", - "hz_advertised_raw": "Unknown", - "count": 16 - }, - "kornia": "0.1.10" - }, - "benchmark_results": { - "images_per_second_mean": 391.41907068301214, - "images_per_second_std": 0.6161184414713902, - "images_per_second_p50": 391.45582912517824, - "images_per_second_p90": 392.01595993194485, - "images_per_second_p99": 392.3161598240961, - "us_per_image_mean": 2554.8128676653505, - "us_per_image_p50": 2554.566634592696, - "us_per_image_p90": 2558.9115690273757, - "us_per_image_p99": 2561.1611942454792, - "raw_times_s": [ - 127.43484582400015, - 127.70626912499938, - 127.7555545209998, - 128.06799621899972, - 127.7257771630002 - ], - "raw_throughput_ips": [ - 392.3495153676684, - 391.51562677835955, - 391.36458831448635, - 390.4097938293683, - 391.45582912517824 - ], - "num_images_total": 50000, - "num_images_decoded": 49999, - "num_images_skipped": 1, - "skip_rate": 2e-05, - "skip_indices": [ - 19876 - ], - "skip_examples": [ - "idx=19876: ValueError: Unsupported number of channels: 4" - ] - }, - "num_images": 50000, - "num_runs": 5 -} \ No newline at end of file diff --git a/output/linux_Neoverse-N1/kornia_dataloader_results.json b/output/linux_Neoverse-N1/kornia_dataloader_results.json deleted file mode 100644 index 8ebf1b9..0000000 --- a/output/linux_Neoverse-N1/kornia_dataloader_results.json +++ /dev/null @@ -1,125 +0,0 @@ -{ - "library": "kornia", - "benchmark_type": "dataloader", - "batch_size": 32, - "num_threads": 1, - "system_info": { - "Python": "3.12.3", - "OS": "Linux", - "OS Version": "#12~24.04.1-Ubuntu SMP Fri Mar 27 23:33:21 UTC 2026", - "Machine": "aarch64", - "CPU": { - "brand_raw": "Neoverse-N1", - "arch": "ARM_8", - "hz_advertised_raw": "Unknown", - "count": 16 - }, - "kornia": "0.1.10" - }, - "worker_results": [ - { - "num_workers": 0, - "num_warmup": 1, - "images_per_second_mean": 383.7951227114674, - "images_per_second_std": 0.5814580684903968, - "images_per_second_p50": 383.6882393749202, - "images_per_second_p90": 384.3813765363367, - "images_per_second_p99": 384.5373323976554, - "us_per_image_mean": 2605.5627994893307, - "us_per_image_p50": 2606.2826466329398, - "us_per_image_p90": 2609.252684689695, - "us_per_image_p99": 2609.920943252465, - "raw_times_s": [ - 130.49714971499998, - 130.31152604900035, - 130.0179274710008 - ], - "raw_throughput_ips": [ - 383.14246793279096, - 383.6882393749202, - 384.5546608266908 - ] - }, - { - "num_workers": 2, - "num_warmup": 1, - "images_per_second_mean": 693.6291651816476, - "images_per_second_std": 0.4850312945752785, - "images_per_second_p50": 693.6066825441661, - "images_per_second_p90": 694.1086380425904, - "images_per_second_p99": 694.2215780297358, - "us_per_image_mean": 1441.6932550517586, - "us_per_image_p50": 1441.7392813056179, - "us_per_image_p90": 1442.671246104909, - "us_per_image_p99": 1442.8809381847495, - "raw_times_s": [ - 72.08552232599959, - 72.1437689609993, - 72.02037189099974 - ], - "raw_throughput_ips": [ - 693.6066825441661, - 693.04668608358, - 694.2341269171965 - ] - }, - { - "num_workers": 4, - "num_warmup": 1, - "images_per_second_mean": 1046.8098695148146, - "images_per_second_std": 14.309331513185342, - "images_per_second_p50": 1044.6954137708824, - "images_per_second_p90": 1061.1762410948063, - "images_per_second_p99": 1064.8844272426893, - "us_per_image_mean": 955.4613284065722, - "us_per_image_p50": 957.2167990959663, - "us_per_image_p90": 967.8124344646931, - "us_per_image_p99": 970.1964524226566, - "raw_times_s": [ - 47.859882737999214, - 48.52209670400043, - 46.93435343500096 - ], - "raw_throughput_ips": [ - 1044.6954137708824, - 1030.437746847774, - 1065.2964479257873 - ] - }, - { - "num_workers": 8, - "num_warmup": 1, - "images_per_second_mean": 1260.4825464739358, - "images_per_second_std": 14.457864622185644, - "images_per_second_p50": 1256.4580413241677, - "images_per_second_p90": 1275.1760855739042, - "images_per_second_p99": 1279.387645530095, - "us_per_image_mean": 793.4508845776933, - "us_per_image_p50": 795.8880974219487, - "us_per_image_p90": 801.678747122951, - "us_per_image_p99": 802.9816433056765, - "raw_times_s": [ - 39.79360898300001, - 40.155517351000526, - 39.06612599999971 - ], - "raw_throughput_ips": [ - 1256.4580413241677, - 1245.1340014613013, - 1279.8555966363383 - ] - } - ], - "num_images": 49999, - "num_images_total": 50000, - "num_images_decoded": 49999, - "num_images_skipped": 1, - "skip_rate": 2e-05, - "skip_indices": [ - 19876 - ], - "skip_examples": [ - "idx=19876: ValueError: Unsupported number of channels: 4" - ], - "num_runs": 3 -} \ No newline at end of file diff --git a/output/linux_Neoverse-N1/opencv_1t_results.json b/output/linux_Neoverse-N1/opencv_1t_results.json deleted file mode 100644 index b5a26cd..0000000 --- a/output/linux_Neoverse-N1/opencv_1t_results.json +++ /dev/null @@ -1,53 +0,0 @@ -{ - "library": "opencv", - "mode": "memory", - "run_tag": "1t", - "requested_threads": 1, - "effective_threads": 1, - "system_info": { - "Python": "3.12.3", - "OS": "Linux", - "OS Version": "#12~24.04.1-Ubuntu SMP Fri Mar 27 23:33:21 UTC 2026", - "Machine": "aarch64", - "CPU": { - "brand_raw": "Neoverse-N1", - "arch": "ARM_8", - "hz_advertised_raw": "Unknown", - "count": 16 - }, - "opencv": "4.13.0.92" - }, - "benchmark_results": { - "images_per_second_mean": 445.4704160751162, - "images_per_second_std": 0.09840803368305598, - "images_per_second_p50": 445.48763483997357, - "images_per_second_p90": 445.56491890081395, - "images_per_second_p99": 445.60757430269905, - "us_per_image_mean": 2244.8180905359986, - "us_per_image_p50": 2244.7312153999883, - "us_per_image_p90": 2245.352065404004, - "us_per_image_p99": 2245.618585982411, - "raw_times_s": [ - 112.23656076999941, - 112.24539322199962, - 112.20515782999973, - 112.23500084300031, - 112.28240996900058 - ], - "raw_throughput_ips": [ - 445.48763483997357, - 445.4525799656623, - 445.6123137917974, - 445.4938265643388, - 445.3057252138088 - ], - "num_images_total": 50000, - "num_images_decoded": 50000, - "num_images_skipped": 0, - "skip_rate": 0.0, - "skip_indices": [], - "skip_examples": [] - }, - "num_images": 50000, - "num_runs": 5 -} \ No newline at end of file diff --git a/output/linux_Neoverse-N1/opencv_dataloader_results.json b/output/linux_Neoverse-N1/opencv_dataloader_results.json deleted file mode 100644 index 5d5b2e8..0000000 --- a/output/linux_Neoverse-N1/opencv_dataloader_results.json +++ /dev/null @@ -1,121 +0,0 @@ -{ - "library": "opencv", - "benchmark_type": "dataloader", - "batch_size": 32, - "num_threads": 16, - "system_info": { - "Python": "3.12.3", - "OS": "Linux", - "OS Version": "#12~24.04.1-Ubuntu SMP Fri Mar 27 23:33:21 UTC 2026", - "Machine": "aarch64", - "CPU": { - "brand_raw": "Neoverse-N1", - "arch": "ARM_8", - "hz_advertised_raw": "Unknown", - "count": 16 - }, - "opencv": "4.13.0.92" - }, - "worker_results": [ - { - "num_workers": 0, - "num_warmup": 1, - "images_per_second_mean": 441.65948639702884, - "images_per_second_std": 0.24760541865808255, - "images_per_second_p50": 441.52179557716977, - "images_per_second_p90": 441.9100848665587, - "images_per_second_p99": 441.9974499566712, - "us_per_image_mean": 2264.1884641533275, - "us_per_image_p50": 2264.8938512599852, - "us_per_image_p90": 2265.1905597239966, - "us_per_image_p99": 2265.257319128399, - "raw_times_s": [ - 113.12034021799991, - 113.26323684199997, - 113.24469256299926 - ], - "raw_throughput_ips": [ - 442.0071571889059, - 441.44950642501095, - 441.52179557716977 - ] - }, - { - "num_workers": 2, - "num_warmup": 1, - "images_per_second_mean": 775.1662490382784, - "images_per_second_std": 0.7958315483788737, - "images_per_second_p50": 774.9326374883431, - "images_per_second_p90": 775.9757414632103, - "images_per_second_p99": 776.2104398575555, - "us_per_image_mean": 1290.047205773347, - "us_per_image_p50": 1290.4347444200175, - "us_per_image_p90": 1291.2387334600135, - "us_per_image_p99": 1291.4196309940126, - "raw_times_s": [ - 64.41335710900057, - 64.57198653600062, - 64.52173722100088 - ], - "raw_throughput_ips": [ - 776.2365174569271, - 774.329592169565, - 774.9326374883431 - ] - }, - { - "num_workers": 4, - "num_warmup": 1, - "images_per_second_mean": 1408.0200479299185, - "images_per_second_std": 15.900825567333639, - "images_per_second_p50": 1409.603689443964, - "images_per_second_p90": 1423.244201254619, - "images_per_second_p99": 1426.3133164120163, - "us_per_image_mean": 710.3079053533414, - "us_per_image_p50": 709.4192555600239, - "us_per_image_p90": 718.3349067119925, - "us_per_image_p99": 720.3409282211854, - "raw_times_s": [ - 36.028190974999234, - 35.470962778001194, - 35.04703205000078 - ], - "raw_throughput_ips": [ - 1387.8021251385094, - 1409.603689443964, - 1426.6543292072827 - ] - }, - { - "num_workers": 8, - "num_warmup": 1, - "images_per_second_mean": 1419.1876880213747, - "images_per_second_std": 35.78822636874506, - "images_per_second_p50": 1421.996799473029, - "images_per_second_p90": 1453.6369715308435, - "images_per_second_p99": 1460.756010243852, - "us_per_image_mean": 705.0782756200109, - "us_per_image_p50": 703.2364632399913, - "us_per_image_p90": 722.8807657040234, - "us_per_image_p99": 727.3007337584306, - "raw_times_s": [ - 34.2103261150005, - 35.161823161999564, - 36.38959206600157 - ], - "raw_throughput_ips": [ - 1461.5470145452973, - 1421.996799473029, - 1374.0192500457981 - ] - } - ], - "num_images": 50000, - "num_images_total": 50000, - "num_images_decoded": 50000, - "num_images_skipped": 0, - "skip_rate": 0.0, - "skip_indices": [], - "skip_examples": [], - "num_runs": 3 -} \ No newline at end of file diff --git a/output/linux_Neoverse-N1/opencv_default_results.json b/output/linux_Neoverse-N1/opencv_default_results.json deleted file mode 100644 index 4d211dd..0000000 --- a/output/linux_Neoverse-N1/opencv_default_results.json +++ /dev/null @@ -1,53 +0,0 @@ -{ - "library": "opencv", - "mode": "memory", - "run_tag": "default", - "requested_threads": 0, - "effective_threads": 16, - "system_info": { - "Python": "3.12.3", - "OS": "Linux", - "OS Version": "#12~24.04.1-Ubuntu SMP Fri Mar 27 23:33:21 UTC 2026", - "Machine": "aarch64", - "CPU": { - "brand_raw": "Neoverse-N1", - "arch": "ARM_8", - "hz_advertised_raw": "Unknown", - "count": 16 - }, - "opencv": "4.13.0.92" - }, - "benchmark_results": { - "images_per_second_mean": 445.83733756990443, - "images_per_second_std": 0.47059072784927625, - "images_per_second_p50": 445.6807549672064, - "images_per_second_p90": 446.4098778981883, - "images_per_second_p99": 446.6256688655102, - "us_per_image_mean": 2242.9730060879956, - "us_per_image_p50": 2243.7585398399824, - "us_per_image_p90": 2245.1709170200047, - "us_per_image_p99": 2245.218842631999, - "raw_times_s": [ - 111.94456435400025, - 112.0949997409989, - 112.25455205000071, - 112.18792699199912, - 112.26120838499992 - ], - "raw_throughput_ips": [ - 446.6496456396571, - 446.0502262859851, - 445.41623557260175, - 445.6807549672064, - 445.3898253840717 - ], - "num_images_total": 50000, - "num_images_decoded": 50000, - "num_images_skipped": 0, - "skip_rate": 0.0, - "skip_indices": [], - "skip_examples": [] - }, - "num_images": 50000, - "num_runs": 5 -} \ No newline at end of file diff --git a/output/linux_Neoverse-N1/pillow_1t_results.json b/output/linux_Neoverse-N1/pillow_1t_results.json deleted file mode 100644 index af943d8..0000000 --- a/output/linux_Neoverse-N1/pillow_1t_results.json +++ /dev/null @@ -1,53 +0,0 @@ -{ - "library": "pillow", - "mode": "memory", - "run_tag": "1t", - "requested_threads": 1, - "effective_threads": 1, - "system_info": { - "Python": "3.12.3", - "OS": "Linux", - "OS Version": "#12~24.04.1-Ubuntu SMP Fri Mar 27 23:33:21 UTC 2026", - "Machine": "aarch64", - "CPU": { - "brand_raw": "Neoverse-N1", - "arch": "ARM_8", - "hz_advertised_raw": "Unknown", - "count": 16 - }, - "pillow": "12.2.0" - }, - "benchmark_results": { - "images_per_second_mean": 360.31222840842656, - "images_per_second_std": 0.301432614953004, - "images_per_second_p50": 360.27151954839474, - "images_per_second_p90": 360.6409645332478, - "images_per_second_p99": 360.71547221738626, - "us_per_image_mean": 2775.372638463996, - "us_per_image_p50": 2775.684298479973, - "us_per_image_p90": 2777.8898770840096, - "us_per_image_p99": 2778.9722173864166, - "raw_times_s": [ - 138.78421492399866, - 138.95462387100088, - 138.61022425699957, - 138.68979773499996, - 138.8042988289999 - ], - "raw_throughput_ips": [ - 360.27151954839474, - 359.82969553008695, - 360.7237508489572, - 360.5167850596838, - 360.21939105501014 - ], - "num_images_total": 50000, - "num_images_decoded": 50000, - "num_images_skipped": 0, - "skip_rate": 0.0, - "skip_indices": [], - "skip_examples": [] - }, - "num_images": 50000, - "num_runs": 5 -} \ No newline at end of file diff --git a/output/linux_Neoverse-N1/pillow_dataloader_results.json b/output/linux_Neoverse-N1/pillow_dataloader_results.json deleted file mode 100644 index a0958be..0000000 --- a/output/linux_Neoverse-N1/pillow_dataloader_results.json +++ /dev/null @@ -1,121 +0,0 @@ -{ - "library": "pillow", - "benchmark_type": "dataloader", - "batch_size": 32, - "num_threads": 1, - "system_info": { - "Python": "3.12.3", - "OS": "Linux", - "OS Version": "#12~24.04.1-Ubuntu SMP Fri Mar 27 23:33:21 UTC 2026", - "Machine": "aarch64", - "CPU": { - "brand_raw": "Neoverse-N1", - "arch": "ARM_8", - "hz_advertised_raw": "Unknown", - "count": 16 - }, - "pillow": "12.2.0" - }, - "worker_results": [ - { - "num_workers": 0, - "num_warmup": 1, - "images_per_second_mean": 357.43206768384744, - "images_per_second_std": 0.8215238655466627, - "images_per_second_p50": 357.77555271001614, - "images_per_second_p90": 358.13231414913616, - "images_per_second_p99": 358.21258547293814, - "us_per_image_mean": 2797.7492224399975, - "us_per_image_p50": 2795.048438679987, - "us_per_image_p90": 2804.313975879988, - "us_per_image_p99": 2806.398721749988, - "raw_times_s": [ - 139.75242193399936, - 140.3315180089994, - 139.57844342300086 - ], - "raw_throughput_ips": [ - 357.77555271001614, - 356.2991458326099, - 358.22150450891615 - ] - }, - { - "num_workers": 2, - "num_warmup": 1, - "images_per_second_mean": 626.2981291490347, - "images_per_second_std": 1.3681520145901125, - "images_per_second_p50": 625.6447192894645, - "images_per_second_p90": 627.6905690233317, - "images_per_second_p99": 628.1508852134518, - "us_per_image_mean": 1596.6912868266688, - "us_per_image_p50": 1598.3512194200011, - "us_per_image_p90": 1599.5726913560065, - "us_per_image_p99": 1599.8475225416078, - "raw_times_s": [ - 79.59222908599986, - 79.91756097100006, - 79.9939029670004 - ], - "raw_throughput_ips": [ - 628.2020314567985, - 625.6447192894645, - 625.0476367008412 - ] - }, - { - "num_workers": 4, - "num_warmup": 1, - "images_per_second_mean": 1193.094720131601, - "images_per_second_std": 3.25641858249844, - "images_per_second_p50": 1192.821622056176, - "images_per_second_p90": 1196.3343502453874, - "images_per_second_p99": 1197.12471408796, - "us_per_image_mean": 838.1626623800063, - "us_per_image_p50": 838.3483175600122, - "us_per_image_p90": 840.3625337520207, - "us_per_image_p99": 840.8157323952225, - "raw_times_s": [ - 41.91741587800061, - 42.04330439000114, - 41.7636790889992 - ], - "raw_throughput_ips": [ - 1192.821622056176, - 1189.2500060459363, - 1197.2125322926902 - ] - }, - { - "num_workers": 8, - "num_warmup": 1, - "images_per_second_mean": 1386.6940106776335, - "images_per_second_std": 11.178300427067269, - "images_per_second_p50": 1389.5992232934652, - "images_per_second_p90": 1396.8788819315475, - "images_per_second_p99": 1398.516805125116, - "us_per_image_mean": 721.1866318466613, - "us_per_image_p50": 719.6319508799934, - "us_per_image_p90": 727.1085769599886, - "us_per_image_p99": 728.7908178279874, - "raw_times_s": [ - 35.98159754399967, - 36.44888667399937, - 35.74751055900015 - ], - "raw_throughput_ips": [ - 1389.5992232934652, - 1371.7840121483669, - 1398.698796591068 - ] - } - ], - "num_images": 50000, - "num_images_total": 50000, - "num_images_decoded": 50000, - "num_images_skipped": 0, - "skip_rate": 0.0, - "skip_indices": [], - "skip_examples": [], - "num_runs": 3 -} \ No newline at end of file diff --git a/output/linux_Neoverse-N1/pyvips_1t_results.json b/output/linux_Neoverse-N1/pyvips_1t_results.json deleted file mode 100644 index 03311d4..0000000 --- a/output/linux_Neoverse-N1/pyvips_1t_results.json +++ /dev/null @@ -1,53 +0,0 @@ -{ - "library": "pyvips", - "mode": "memory", - "run_tag": "1t", - "requested_threads": 1, - "effective_threads": 1, - "system_info": { - "Python": "3.12.3", - "OS": "Linux", - "OS Version": "#12~24.04.1-Ubuntu SMP Fri Mar 27 23:33:21 UTC 2026", - "Machine": "aarch64", - "CPU": { - "brand_raw": "Neoverse-N1", - "arch": "ARM_8", - "hz_advertised_raw": "Unknown", - "count": 16 - }, - "pyvips": "3.1.1" - }, - "benchmark_results": { - "images_per_second_mean": 261.38518670789074, - "images_per_second_std": 0.41676333478888505, - "images_per_second_p50": 261.39329834011613, - "images_per_second_p90": 261.7981078049998, - "images_per_second_p99": 262.0160849197778, - "us_per_image_mean": 3825.781234604008, - "us_per_image_p50": 3825.652785860002, - "us_per_image_p90": 3831.8877285400013, - "us_per_image_p99": 3835.0812788199923, - "raw_times_s": [ - 191.2826392930001, - 191.77180588699957, - 190.8103414720008, - 191.25226476200078, - 191.3282572370008 - ], - "raw_throughput_ips": [ - 261.39329834011613, - 260.7265430324112, - 262.0403045991976, - 261.43481261370306, - 261.33097495402546 - ], - "num_images_total": 50000, - "num_images_decoded": 50000, - "num_images_skipped": 0, - "skip_rate": 0.0, - "skip_indices": [], - "skip_examples": [] - }, - "num_images": 50000, - "num_runs": 5 -} \ No newline at end of file diff --git a/output/linux_Neoverse-N1/pyvips_default_results.json b/output/linux_Neoverse-N1/pyvips_default_results.json deleted file mode 100644 index 1024e62..0000000 --- a/output/linux_Neoverse-N1/pyvips_default_results.json +++ /dev/null @@ -1,53 +0,0 @@ -{ - "library": "pyvips", - "mode": "memory", - "run_tag": "default", - "requested_threads": 0, - "effective_threads": 16, - "system_info": { - "Python": "3.12.3", - "OS": "Linux", - "OS Version": "#12~24.04.1-Ubuntu SMP Fri Mar 27 23:33:21 UTC 2026", - "Machine": "aarch64", - "CPU": { - "brand_raw": "Neoverse-N1", - "arch": "ARM_8", - "hz_advertised_raw": "Unknown", - "count": 16 - }, - "pyvips": "3.1.1" - }, - "benchmark_results": { - "images_per_second_mean": 238.03803673711968, - "images_per_second_std": 0.43592257015923264, - "images_per_second_p50": 237.94520651437156, - "images_per_second_p90": 238.53972515530535, - "images_per_second_p99": 238.6862937759095, - "us_per_image_mean": 4201.02336005601, - "us_per_image_p50": 4202.648225820012, - "us_per_image_p90": 4209.09282498002, - "us_per_image_p99": 4211.79673177203, - "raw_times_s": [ - 210.6048582930016, - 210.1324112910006, - 209.4656881050014, - 210.2293156830001, - 209.8235666419987 - ], - "raw_throughput_ips": [ - 237.41142728264165, - 237.94520651437156, - 238.70257917819885, - 237.83552658942125, - 238.2954441209651 - ], - "num_images_total": 50000, - "num_images_decoded": 50000, - "num_images_skipped": 0, - "skip_rate": 0.0, - "skip_indices": [], - "skip_examples": [] - }, - "num_images": 50000, - "num_runs": 5 -} \ No newline at end of file diff --git a/output/linux_Neoverse-N1/run_summary.json b/output/linux_Neoverse-N1/run_summary.json deleted file mode 100644 index 25e84e5..0000000 --- a/output/linux_Neoverse-N1/run_summary.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "timestamp_utc": "2026-05-20T09:31:31.728687+00:00", - "system": "linux_Neoverse-N1", - "mode": "both", - "num_images": 50000, - "num_runs": 5, - "dataloader_runs": 3, - "workers": [ - 0, - 2, - 4, - 8 - ], - "libs_requested": [ - "ajpegli" - ], - "libs_skipped_platform": [], - "libs_run": [ - "ajpegli" - ], - "failures": [], - "exit_status": "ok" -} \ No newline at end of file diff --git a/output/linux_Neoverse-N1/simplejpeg_1t_results.json b/output/linux_Neoverse-N1/simplejpeg_1t_results.json deleted file mode 100644 index 49f1c3e..0000000 --- a/output/linux_Neoverse-N1/simplejpeg_1t_results.json +++ /dev/null @@ -1,53 +0,0 @@ -{ - "library": "simplejpeg", - "mode": "memory", - "run_tag": "1t", - "requested_threads": 1, - "effective_threads": 1, - "system_info": { - "Python": "3.12.3", - "OS": "Linux", - "OS Version": "#12~24.04.1-Ubuntu SMP Fri Mar 27 23:33:21 UTC 2026", - "Machine": "aarch64", - "CPU": { - "brand_raw": "Neoverse-N1", - "arch": "ARM_8", - "hz_advertised_raw": "Unknown", - "count": 16 - }, - "simplejpeg": "1.9.0" - }, - "benchmark_results": { - "images_per_second_mean": 455.8678821707702, - "images_per_second_std": 0.19132898680948834, - "images_per_second_p50": 455.8964650097492, - "images_per_second_p90": 456.0683704033189, - "images_per_second_p99": 456.14792930367406, - "us_per_image_mean": 2193.6184040680237, - "us_per_image_p50": 2193.4804868000356, - "us_per_image_p90": 2194.6417651120137, - "us_per_image_p99": 2194.987660283214, - "raw_times_s": [ - 109.70326365800065, - 109.7513046540007, - 109.67402434000178, - 109.66456910100169, - 109.61143926400109 - ], - "raw_throughput_ips": [ - 455.77495447970205, - 455.5754499468483, - 455.8964650097492, - 455.9357722360603, - 456.1567691814913 - ], - "num_images_total": 50000, - "num_images_decoded": 50000, - "num_images_skipped": 0, - "skip_rate": 0.0, - "skip_indices": [], - "skip_examples": [] - }, - "num_images": 50000, - "num_runs": 5 -} \ No newline at end of file diff --git a/output/linux_Neoverse-N1/simplejpeg_dataloader_results.json b/output/linux_Neoverse-N1/simplejpeg_dataloader_results.json deleted file mode 100644 index 1e07d4a..0000000 --- a/output/linux_Neoverse-N1/simplejpeg_dataloader_results.json +++ /dev/null @@ -1,121 +0,0 @@ -{ - "library": "simplejpeg", - "benchmark_type": "dataloader", - "batch_size": 32, - "num_threads": 1, - "system_info": { - "Python": "3.12.3", - "OS": "Linux", - "OS Version": "#12~24.04.1-Ubuntu SMP Fri Mar 27 23:33:21 UTC 2026", - "Machine": "aarch64", - "CPU": { - "brand_raw": "Neoverse-N1", - "arch": "ARM_8", - "hz_advertised_raw": "Unknown", - "count": 16 - }, - "simplejpeg": "1.9.0" - }, - "worker_results": [ - { - "num_workers": 0, - "num_warmup": 1, - "images_per_second_mean": 453.94782883959715, - "images_per_second_std": 0.13942869353144943, - "images_per_second_p50": 453.8826612115268, - "images_per_second_p90": 454.08979751432696, - "images_per_second_p99": 454.136403182457, - "us_per_image_mean": 2202.8965242066624, - "us_per_image_p50": 2203.2126041800075, - "us_per_image_p90": 2203.458908804023, - "us_per_image_p99": 2203.5143273444264, - "raw_times_s": [ - 110.17602424800134, - 110.09782417399765, - 110.16063020900037 - ], - "raw_throughput_ips": [ - 453.8192437172376, - 454.141581590027, - 453.8826612115268 - ] - }, - { - "num_workers": 2, - "num_warmup": 1, - "images_per_second_mean": 793.9879495310647, - "images_per_second_std": 0.5651570072274984, - "images_per_second_p50": 794.1161920627931, - "images_per_second_p90": 794.5088650141771, - "images_per_second_p99": 794.5972164282385, - "us_per_image_mean": 1259.4655968800118, - "us_per_image_p50": 1259.2615664999903, - "us_per_image_p90": 1260.3735318599938, - "us_per_image_p99": 1260.6237240659946, - "raw_times_s": [ - 62.92418504700254, - 62.963078324999515, - 63.032576159999735 - ], - "raw_throughput_ips": [ - 794.6070332520231, - 794.1161920627931, - 793.2406232783777 - ] - }, - { - "num_workers": 4, - "num_warmup": 1, - "images_per_second_mean": 1331.476085079502, - "images_per_second_std": 20.931846002629147, - "images_per_second_p50": 1337.715174276232, - "images_per_second_p90": 1350.2765065062958, - "images_per_second_p99": 1353.1028062580604, - "us_per_image_mean": 751.2330721533363, - "us_per_image_p50": 747.5432881600136, - "us_per_image_p90": 763.3368684320158, - "us_per_image_p99": 766.8904239932162, - "raw_times_s": [ - 37.37716440800068, - 38.364263175000815, - 36.94353323999894 - ], - "raw_throughput_ips": [ - 1337.715174276232, - 1303.2962413984624, - 1353.416839563812 - ] - }, - { - "num_workers": 8, - "num_warmup": 1, - "images_per_second_mean": 1557.3939877083542, - "images_per_second_std": 25.01927948973698, - "images_per_second_p50": 1553.7956334140845, - "images_per_second_p90": 1582.500348760778, - "images_per_second_p99": 1588.9589097137841, - "us_per_image_mean": 642.2634889666612, - "us_per_image_p50": 643.5852814200189, - "us_per_image_p90": 652.0341691159847, - "us_per_image_p99": 653.935168847577, - "raw_times_s": [ - 32.179264071000944, - 32.70731955199881, - 31.452939721999428 - ], - "raw_throughput_ips": [ - 1553.7956334140845, - 1528.7098021135273, - 1589.6765275974515 - ] - } - ], - "num_images": 50000, - "num_images_total": 50000, - "num_images_decoded": 50000, - "num_images_skipped": 0, - "skip_rate": 0.0, - "skip_indices": [], - "skip_examples": [], - "num_runs": 3 -} \ No newline at end of file diff --git a/output/linux_Neoverse-N1/skimage_1t_results.json b/output/linux_Neoverse-N1/skimage_1t_results.json deleted file mode 100644 index 594a7e0..0000000 --- a/output/linux_Neoverse-N1/skimage_1t_results.json +++ /dev/null @@ -1,53 +0,0 @@ -{ - "library": "skimage", - "mode": "memory", - "run_tag": "1t", - "requested_threads": 1, - "effective_threads": 1, - "system_info": { - "Python": "3.12.3", - "OS": "Linux", - "OS Version": "#12~24.04.1-Ubuntu SMP Fri Mar 27 23:33:21 UTC 2026", - "Machine": "aarch64", - "CPU": { - "brand_raw": "Neoverse-N1", - "arch": "ARM_8", - "hz_advertised_raw": "Unknown", - "count": 16 - }, - "skimage": "0.26.0" - }, - "benchmark_results": { - "images_per_second_mean": 326.40839706414437, - "images_per_second_std": 0.34621191946065444, - "images_per_second_p50": 326.5672543960418, - "images_per_second_p90": 326.74271670210334, - "images_per_second_p99": 326.7802361019789, - "us_per_image_mean": 3063.6501222480147, - "us_per_image_p50": 3062.1563752600196, - "us_per_image_p90": 3067.680923012027, - "us_per_image_p99": 3068.557487523221, - "raw_times_s": [ - 153.0548909680001, - 153.31099910800185, - 153.43274417900102, - 153.10781876300098, - 153.0060775439997 - ], - "raw_throughput_ips": [ - 326.68018436897734, - 326.1344606121631, - 325.87568101935216, - 326.5672543960418, - 326.7844049241873 - ], - "num_images_total": 50000, - "num_images_decoded": 50000, - "num_images_skipped": 0, - "skip_rate": 0.0, - "skip_indices": [], - "skip_examples": [] - }, - "num_images": 50000, - "num_runs": 5 -} \ No newline at end of file diff --git a/output/linux_Neoverse-N1/skimage_dataloader_results.json b/output/linux_Neoverse-N1/skimage_dataloader_results.json deleted file mode 100644 index cb561a4..0000000 --- a/output/linux_Neoverse-N1/skimage_dataloader_results.json +++ /dev/null @@ -1,121 +0,0 @@ -{ - "library": "skimage", - "benchmark_type": "dataloader", - "batch_size": 32, - "num_threads": 1, - "system_info": { - "Python": "3.12.3", - "OS": "Linux", - "OS Version": "#12~24.04.1-Ubuntu SMP Fri Mar 27 23:33:21 UTC 2026", - "Machine": "aarch64", - "CPU": { - "brand_raw": "Neoverse-N1", - "arch": "ARM_8", - "hz_advertised_raw": "Unknown", - "count": 16 - }, - "skimage": "0.26.0" - }, - "worker_results": [ - { - "num_workers": 0, - "num_warmup": 1, - "images_per_second_mean": 324.4877343523228, - "images_per_second_std": 0.7104568719896657, - "images_per_second_p50": 324.4635913509852, - "images_per_second_p90": 325.18846468682614, - "images_per_second_p99": 325.35156118739036, - "us_per_image_mean": 3081.7953574866747, - "us_per_image_p50": 3082.0098977399903, - "us_per_image_p90": 3088.3612425560277, - "us_per_image_p99": 3089.7902951396363, - "raw_times_s": [ - 154.1004948869995, - 154.49745393800185, - 153.6713547979998 - ], - "raw_throughput_ips": [ - 324.4635913509852, - 323.62992868519666, - 325.3696830207864 - ] - }, - { - "num_workers": 2, - "num_warmup": 1, - "images_per_second_mean": 572.2982989266062, - "images_per_second_std": 2.0158918760365965, - "images_per_second_p50": 571.6763383121689, - "images_per_second_p90": 574.3502767027911, - "images_per_second_p99": 574.9519128406811, - "us_per_image_mean": 1747.3621550399791, - "us_per_image_p50": 1749.2415427800006, - "us_per_image_p90": 1752.865293083989, - "us_per_image_p99": 1753.6806369023864, - "raw_times_s": [ - 87.6885615329993, - 87.46207713900003, - 86.95368458399753 - ], - "raw_throughput_ips": [ - 570.199797167203, - 571.6763383121689, - 575.0187613004466 - ] - }, - { - "num_workers": 4, - "num_warmup": 1, - "images_per_second_mean": 1067.9544671904762, - "images_per_second_std": 2.6771736856980355, - "images_per_second_p50": 1068.2657249039355, - "images_per_second_p90": 1070.506420217218, - "images_per_second_p99": 1071.0105766627064, - "us_per_image_mean": 936.3753958333109, - "us_per_image_p50": 936.0966814599669, - "us_per_image_p90": 938.7239311879821, - "us_per_image_p99": 939.3150623767855, - "raw_times_s": [ - 46.969037180999294, - 46.682438120998995, - 46.80483407299835 - ], - "raw_throughput_ips": [ - 1064.5310826219543, - 1071.0665940455385, - 1068.2657249039355 - ] - }, - { - "num_workers": 8, - "num_warmup": 1, - "images_per_second_mean": 1387.6629955259607, - "images_per_second_std": 6.2226968253914485, - "images_per_second_p50": 1388.8274174584237, - "images_per_second_p90": 1393.473475756951, - "images_per_second_p99": 1394.5188388741196, - "us_per_image_mean": 720.6505740199888, - "us_per_image_p50": 720.0318682000216, - "us_per_image_p90": 723.9154615119623, - "us_per_image_p99": 724.7892700071491, - "raw_times_s": [ - 36.24431799199738, - 35.851674700999865, - 36.00159341000108 - ], - "raw_throughput_ips": [ - 1379.526578787876, - 1394.634990331583, - 1388.8274174584237 - ] - } - ], - "num_images": 50000, - "num_images_total": 50000, - "num_images_decoded": 50000, - "num_images_skipped": 0, - "skip_rate": 0.0, - "skip_indices": [], - "skip_examples": [], - "num_runs": 3 -} \ No newline at end of file diff --git a/output/linux_Neoverse-N1/tensorflow_1t_results.json b/output/linux_Neoverse-N1/tensorflow_1t_results.json deleted file mode 100644 index 51321cb..0000000 --- a/output/linux_Neoverse-N1/tensorflow_1t_results.json +++ /dev/null @@ -1,53 +0,0 @@ -{ - "library": "tensorflow", - "mode": "memory", - "run_tag": "1t", - "requested_threads": 1, - "effective_threads": 1, - "system_info": { - "Python": "3.12.3", - "OS": "Linux", - "OS Version": "#12~24.04.1-Ubuntu SMP Fri Mar 27 23:33:21 UTC 2026", - "Machine": "aarch64", - "CPU": { - "brand_raw": "Neoverse-N1", - "arch": "ARM_8", - "hz_advertised_raw": "Unknown", - "count": 16 - }, - "tensorflow": "2.21.0" - }, - "benchmark_results": { - "images_per_second_mean": 267.69159756308864, - "images_per_second_std": 0.3398725269853197, - "images_per_second_p50": 267.7959616714494, - "images_per_second_p90": 267.97783051334, - "images_per_second_p99": 268.0284921409596, - "us_per_image_mean": 3735.648123840001, - "us_per_image_p50": 3734.1862579200097, - "us_per_image_p90": 3741.0597979079903, - "us_per_image_p99": 3744.230761492797, - "raw_times_s": [ - 187.22915453899986, - 186.64140674100054, - 186.788742929999, - 186.5434138540004, - 186.70931289600048 - ], - "raw_throughput_ips": [ - 267.05242633344795, - 267.8933944673073, - 267.68208413254337, - 268.0341212106951, - 267.7959616714494 - ], - "num_images_total": 50000, - "num_images_decoded": 50000, - "num_images_skipped": 0, - "skip_rate": 0.0, - "skip_indices": [], - "skip_examples": [] - }, - "num_images": 50000, - "num_runs": 5 -} \ No newline at end of file diff --git a/output/linux_Neoverse-N1/torchvision_1t_results.json b/output/linux_Neoverse-N1/torchvision_1t_results.json deleted file mode 100644 index 654b767..0000000 --- a/output/linux_Neoverse-N1/torchvision_1t_results.json +++ /dev/null @@ -1,53 +0,0 @@ -{ - "library": "torchvision", - "mode": "memory", - "run_tag": "1t", - "requested_threads": 1, - "effective_threads": 1, - "system_info": { - "Python": "3.12.3", - "OS": "Linux", - "OS Version": "#12~24.04.1-Ubuntu SMP Fri Mar 27 23:33:21 UTC 2026", - "Machine": "aarch64", - "CPU": { - "brand_raw": "Neoverse-N1", - "arch": "ARM_8", - "hz_advertised_raw": "Unknown", - "count": 16 - }, - "torchvision": "0.26.0+cpu" - }, - "benchmark_results": { - "images_per_second_mean": 440.05640650922277, - "images_per_second_std": 0.2571015945803752, - "images_per_second_p50": 440.0970680184487, - "images_per_second_p90": 440.34261516990205, - "images_per_second_p99": 440.4355284747239, - "us_per_image_mean": 2272.4367297400167, - "us_per_image_p50": 2272.2259989200393, - "us_per_image_p90": 2273.9217364960205, - "us_per_image_p99": 2274.048321769599, - "raw_times_s": [ - 113.61129994600196, - 113.70311933999983, - 113.58789267199973, - 113.52133242499985, - 113.68553805200281 - ], - "raw_throughput_ips": [ - 440.0970680184487, - 439.7416736693732, - 440.1877596618656, - 440.4458521752597, - 439.8096790211668 - ], - "num_images_total": 50000, - "num_images_decoded": 50000, - "num_images_skipped": 0, - "skip_rate": 0.0, - "skip_indices": [], - "skip_examples": [] - }, - "num_images": 50000, - "num_runs": 5 -} \ No newline at end of file diff --git a/output/linux_Neoverse-N1/torchvision_dataloader_results.json b/output/linux_Neoverse-N1/torchvision_dataloader_results.json deleted file mode 100644 index d6de344..0000000 --- a/output/linux_Neoverse-N1/torchvision_dataloader_results.json +++ /dev/null @@ -1,121 +0,0 @@ -{ - "library": "torchvision", - "benchmark_type": "dataloader", - "batch_size": 32, - "num_threads": 16, - "system_info": { - "Python": "3.12.3", - "OS": "Linux", - "OS Version": "#12~24.04.1-Ubuntu SMP Fri Mar 27 23:33:21 UTC 2026", - "Machine": "aarch64", - "CPU": { - "brand_raw": "Neoverse-N1", - "arch": "ARM_8", - "hz_advertised_raw": "Unknown", - "count": 16 - }, - "torchvision": "0.26.0+cpu" - }, - "worker_results": [ - { - "num_workers": 0, - "num_warmup": 1, - "images_per_second_mean": 431.42274979831353, - "images_per_second_std": 0.670935575558989, - "images_per_second_p50": 431.09146816004977, - "images_per_second_p90": 432.1050171268747, - "images_per_second_p99": 432.33306564441034, - "us_per_image_mean": 2317.91767285334, - "us_per_image_p50": 2319.6933223200176, - "us_per_image_p90": 2320.8696659199923, - "us_per_image_p99": 2321.1343432299864, - "raw_times_s": [ - 115.98466611600088, - 116.0581875909993, - 115.64479722100077 - ], - "raw_throughput_ips": [ - 431.09146816004977, - 430.8183768663097, - 432.358404368581 - ] - }, - { - "num_workers": 2, - "num_warmup": 1, - "images_per_second_mean": 766.1957715337345, - "images_per_second_std": 0.4390210592258364, - "images_per_second_p50": 766.3506681454537, - "images_per_second_p90": 766.5813414760298, - "images_per_second_p99": 766.6332429754095, - "us_per_image_mean": 1305.1498919200094, - "us_per_image_p50": 1304.8856633999821, - "us_per_image_p90": 1305.9124377360422, - "us_per_image_p99": 1306.1434619616557, - "raw_times_s": [ - 65.30845656600286, - 65.24428316999911, - 65.21974405199944 - ], - "raw_throughput_ips": [ - 765.597636647076, - 766.3506681454537, - 766.6390098086739 - ] - }, - { - "num_workers": 4, - "num_warmup": 1, - "images_per_second_mean": 1387.0842269441882, - "images_per_second_std": 6.673328783490549, - "images_per_second_p50": 1387.45127596932, - "images_per_second_p90": 1393.5443702595378, - "images_per_second_p99": 1394.9153164748368, - "us_per_image_mean": 720.953448053333, - "us_per_image_p50": 720.7460307399742, - "us_per_image_p90": 724.391761027975, - "us_per_image_p99": 725.2120503427752, - "raw_times_s": [ - 36.03730153699871, - 36.26515967999876, - 35.840555991002475 - ], - "raw_throughput_ips": [ - 1387.45127596932, - 1378.7337610311527, - 1395.0676438320922 - ] - }, - { - "num_workers": 8, - "num_warmup": 1, - "images_per_second_mean": 1504.4733192544998, - "images_per_second_std": 27.535299302200983, - "images_per_second_p50": 1506.0096228164846, - "images_per_second_p90": 1531.1240274865677, - "images_per_second_p99": 1536.7747685373363, - "us_per_image_mean": 664.9075405199983, - "us_per_image_p50": 664.0063814000314, - "us_per_image_p90": 677.0161103759747, - "us_per_image_p99": 679.9432993955619, - "raw_times_s": [ - 34.013427130998025, - 33.20031907000157, - 32.52238487700015 - ], - "raw_throughput_ips": [ - 1470.0077062929265, - 1506.0096228164846, - 1537.4026286540884 - ] - } - ], - "num_images": 50000, - "num_images_total": 50000, - "num_images_decoded": 50000, - "num_images_skipped": 0, - "skip_rate": 0.0, - "skip_indices": [], - "skip_examples": [], - "num_runs": 3 -} \ No newline at end of file diff --git a/output/linux_Neoverse-N1/torchvision_default_results.json b/output/linux_Neoverse-N1/torchvision_default_results.json deleted file mode 100644 index 14f0e56..0000000 --- a/output/linux_Neoverse-N1/torchvision_default_results.json +++ /dev/null @@ -1,53 +0,0 @@ -{ - "library": "torchvision", - "mode": "memory", - "run_tag": "default", - "requested_threads": 0, - "effective_threads": 16, - "system_info": { - "Python": "3.12.3", - "OS": "Linux", - "OS Version": "#12~24.04.1-Ubuntu SMP Fri Mar 27 23:33:21 UTC 2026", - "Machine": "aarch64", - "CPU": { - "brand_raw": "Neoverse-N1", - "arch": "ARM_8", - "hz_advertised_raw": "Unknown", - "count": 16 - }, - "torchvision": "0.26.0+cpu" - }, - "benchmark_results": { - "images_per_second_mean": 440.8778892096693, - "images_per_second_std": 0.30621653691899225, - "images_per_second_p50": 440.98700365831644, - "images_per_second_p90": 441.1974482571707, - "images_per_second_p99": 441.27785339672937, - "us_per_image_mean": 2268.202844675965, - "us_per_image_p50": 2267.6405238799634, - "us_per_image_p90": 2270.077466171977, - "us_per_image_p99": 2270.309308987181, - "raw_times_s": [ - 113.36237715599782, - 113.38202619399817, - 113.51675346499906, - 113.30500127999767, - 113.48455307399854 - ], - "raw_throughput_ips": [ - 441.0634396912396, - 440.98700365831644, - 440.4636185742983, - 441.2867873011248, - 440.58859682336754 - ], - "num_images_total": 50000, - "num_images_decoded": 50000, - "num_images_skipped": 0, - "skip_rate": 0.0, - "skip_indices": [], - "skip_examples": [] - }, - "num_images": 50000, - "num_runs": 5 -} \ No newline at end of file diff --git a/output/linux_Neoverse-N1/turbojpeg_1t_results.json b/output/linux_Neoverse-N1/turbojpeg_1t_results.json deleted file mode 100644 index 0e5f270..0000000 --- a/output/linux_Neoverse-N1/turbojpeg_1t_results.json +++ /dev/null @@ -1,57 +0,0 @@ -{ - "library": "turbojpeg", - "mode": "memory", - "run_tag": "1t", - "requested_threads": 1, - "effective_threads": 1, - "system_info": { - "Python": "3.12.3", - "OS": "Linux", - "OS Version": "#12~24.04.1-Ubuntu SMP Fri Mar 27 23:33:21 UTC 2026", - "Machine": "aarch64", - "CPU": { - "brand_raw": "Neoverse-N1", - "arch": "ARM_8", - "hz_advertised_raw": "Unknown", - "count": 16 - }, - "turbojpeg": "1.8.3" - }, - "benchmark_results": { - "images_per_second_mean": 425.69061060919796, - "images_per_second_std": 0.6740763890060237, - "images_per_second_p50": 425.92860881649796, - "images_per_second_p90": 426.3755508617412, - "images_per_second_p99": 426.39170186011876, - "us_per_image_mean": 2349.1298223644635, - "us_per_image_p50": 2347.8112981859554, - "us_per_image_p90": 2353.6221634072726, - "us_per_image_p99": 2353.903612118625, - "raw_times_s": [ - 117.26023126600194, - 117.65530095800204, - 117.69439027499902, - 117.38821709799959, - 117.27257034500144 - ], - "raw_throughput_ips": [ - 426.39349641549404, - 424.96172797048496, - 424.8205873124008, - 425.92860881649796, - 426.348632531112 - ], - "num_images_total": 50000, - "num_images_decoded": 49999, - "num_images_skipped": 1, - "skip_rate": 2e-05, - "skip_indices": [ - 19876 - ], - "skip_examples": [ - "idx=19876: OSError: Unsupported color conversion request" - ] - }, - "num_images": 50000, - "num_runs": 5 -} \ No newline at end of file diff --git a/output/linux_Neoverse-N1/turbojpeg_dataloader_results.json b/output/linux_Neoverse-N1/turbojpeg_dataloader_results.json deleted file mode 100644 index d9622bc..0000000 --- a/output/linux_Neoverse-N1/turbojpeg_dataloader_results.json +++ /dev/null @@ -1,125 +0,0 @@ -{ - "library": "turbojpeg", - "benchmark_type": "dataloader", - "batch_size": 32, - "num_threads": 1, - "system_info": { - "Python": "3.12.3", - "OS": "Linux", - "OS Version": "#12~24.04.1-Ubuntu SMP Fri Mar 27 23:33:21 UTC 2026", - "Machine": "aarch64", - "CPU": { - "brand_raw": "Neoverse-N1", - "arch": "ARM_8", - "hz_advertised_raw": "Unknown", - "count": 16 - }, - "turbojpeg": "1.8.3" - }, - "worker_results": [ - { - "num_workers": 0, - "num_warmup": 1, - "images_per_second_mean": 422.8078492839497, - "images_per_second_std": 0.47368204763480876, - "images_per_second_p50": 422.71447265721184, - "images_per_second_p90": 423.2861053765617, - "images_per_second_p99": 423.4147227384154, - "us_per_image_mean": 2365.1435431442046, - "us_per_image_p50": 2365.6630295005803, - "us_per_image_p90": 2367.609927174522, - "us_per_image_p99": 2368.047979151159, - "raw_times_s": [ - 118.40246448299877, - 118.08118574600303, - 118.28078581199952 - ], - "raw_throughput_ips": [ - 422.28006163823795, - 423.4290135563991, - 422.71447265721184 - ] - }, - { - "num_workers": 2, - "num_warmup": 1, - "images_per_second_mean": 737.7172831792832, - "images_per_second_std": 2.236703854842278, - "images_per_second_p50": 736.8132317300478, - "images_per_second_p90": 739.9981939519455, - "images_per_second_p99": 740.7148104518725, - "us_per_image_mean": 1355.5452751921753, - "us_per_image_p50": 1357.1960395607798, - "us_per_image_p90": 1359.0693165623327, - "us_per_image_p99": 1359.4908038876822, - "raw_times_s": [ - 67.49375760800103, - 67.85844478199942, - 67.97552225300024 - ], - "raw_throughput_ips": [ - 740.79443450742, - 736.8132317300478, - 735.544183300382 - ] - }, - { - "num_workers": 4, - "num_warmup": 1, - "images_per_second_mean": 1346.926511264315, - "images_per_second_std": 4.363688334989556, - "images_per_second_p50": 1343.897273283033, - "images_per_second_p90": 1351.257340623057, - "images_per_second_p99": 1352.9133557745624, - "us_per_image_mean": 742.4387772222047, - "us_per_image_p50": 744.1044936099025, - "us_per_image_p90": 744.1542725254312, - "us_per_image_p99": 744.1654727814253, - "raw_times_s": [ - 36.95151699499911, - 37.204480576001515, - 37.20759169599842 - ], - "raw_throughput_ips": [ - 1353.097357458063, - 1343.897273283033, - 1343.7849030518487 - ] - }, - { - "num_workers": 8, - "num_warmup": 1, - "images_per_second_mean": 1297.2584126419636, - "images_per_second_std": 19.43894490160307, - "images_per_second_p50": 1289.7719112166963, - "images_per_second_p90": 1317.0820540241539, - "images_per_second_p99": 1323.226836155832, - "us_per_image_mean": 771.0282336246786, - "us_per_image_p50": 775.3308870377382, - "us_per_image_p90": 780.9983581631836, - "us_per_image_p99": 782.2735391664089, - "raw_times_s": [ - 37.76617405599973, - 38.76576902099987, - 39.119978882001305 - ], - "raw_throughput_ips": [ - 1323.9095897260183, - 1289.7719112166963, - 1278.0937369831765 - ] - } - ], - "num_images": 49999, - "num_images_total": 50000, - "num_images_decoded": 49999, - "num_images_skipped": 1, - "skip_rate": 2e-05, - "skip_indices": [ - 19876 - ], - "skip_examples": [ - "idx=19876: OSError: Unsupported color conversion request" - ], - "num_runs": 3 -} \ No newline at end of file diff --git a/output/linux_Neoverse-V2/ajpegli_1t_results.json b/output/linux_Neoverse-V2/ajpegli_1t_results.json deleted file mode 100644 index 23de595..0000000 --- a/output/linux_Neoverse-V2/ajpegli_1t_results.json +++ /dev/null @@ -1,57 +0,0 @@ -{ - "library": "ajpegli", - "mode": "memory", - "run_tag": "1t", - "requested_threads": 1, - "effective_threads": 1, - "system_info": { - "Python": "3.12.3", - "OS": "Linux", - "OS Version": "#17~24.04.1-Ubuntu SMP Fri May 8 07:28:18 UTC 2026", - "Machine": "aarch64", - "CPU": { - "brand_raw": "Neoverse-V2", - "arch": "ARM_8", - "hz_advertised_raw": "Unknown", - "count": 16 - }, - "ajpegli": "1.0.0" - }, - "benchmark_results": { - "images_per_second_mean": 336.5096555686261, - "images_per_second_std": 0.053186774900635385, - "images_per_second_p50": 336.543795657525, - "images_per_second_p90": 336.5522303497023, - "images_per_second_p99": 336.5539952989524, - "us_per_image_mean": 2971.6830065241297, - "us_per_image_p50": 2971.381475169502, - "us_per_image_p90": 2972.2669766435324, - "us_per_image_p99": 2972.488494308286, - "raw_times_s": [ - 148.56610237699994, - 148.62268285599998, - 148.56151335200002, - 148.59191712899997, - 148.56367750200002 - ], - "raw_throughput_ips": [ - 336.543795657525, - 336.41567383387815, - 336.5541914044246, - 336.48532817968425, - 336.5492887676188 - ], - "num_images_total": 50000, - "num_images_decoded": 49999, - "num_images_skipped": 1, - "skip_rate": 2e-05, - "skip_indices": [ - 19876 - ], - "skip_examples": [ - "idx=19876: DecodeError: jpegli decode failed: ./lib/jpegli/color_transform.cc:549: Unsupported color transform 4 -> 2" - ] - }, - "num_images": 50000, - "num_runs": 5 -} \ No newline at end of file diff --git a/output/linux_Neoverse-V2/ajpegli_dataloader_results.json b/output/linux_Neoverse-V2/ajpegli_dataloader_results.json deleted file mode 100644 index 5362fbd..0000000 --- a/output/linux_Neoverse-V2/ajpegli_dataloader_results.json +++ /dev/null @@ -1,125 +0,0 @@ -{ - "library": "ajpegli", - "benchmark_type": "dataloader", - "batch_size": 32, - "num_threads": 1, - "system_info": { - "Python": "3.12.3", - "OS": "Linux", - "OS Version": "#17~24.04.1-Ubuntu SMP Fri May 8 07:28:18 UTC 2026", - "Machine": "aarch64", - "CPU": { - "brand_raw": "Neoverse-V2", - "arch": "ARM_8", - "hz_advertised_raw": "Unknown", - "count": 16 - }, - "ajpegli": "1.0.0" - }, - "worker_results": [ - { - "num_workers": 0, - "num_warmup": 1, - "images_per_second_mean": 337.17803670826714, - "images_per_second_std": 0.09635851932368043, - "images_per_second_p50": 337.152652797203, - "images_per_second_p90": 337.2758727545173, - "images_per_second_p99": 337.3035972449131, - "us_per_image_mean": 2965.792468049361, - "us_per_image_p50": 2966.015517610354, - "us_per_image_p90": 2966.5637002060053, - "us_per_image_p99": 2966.6870412900266, - "raw_times_s": [ - 148.2978098650001, - 148.23009237299993, - 148.33207059200004 - ], - "raw_throughput_ips": [ - 337.152652797203, - 337.3066777438459, - 337.07477958375233 - ] - }, - { - "num_workers": 2, - "num_warmup": 1, - "images_per_second_mean": 633.8903019941181, - "images_per_second_std": 0.5335495211347878, - "images_per_second_p50": 633.8048328463738, - "images_per_second_p90": 634.4268009639374, - "images_per_second_p99": 634.5667437903892, - "us_per_image_mean": 1577.5611413894949, - "us_per_image_p50": 1577.772759335187, - "us_per_image_p90": 1578.811286185724, - "us_per_image_p99": 1579.0449547270948, - "raw_times_s": [ - 78.79041150700004, - 78.88706019400001, - 78.95196682400001 - ], - "raw_throughput_ips": [ - 634.5822929933282, - 633.8048328463738, - 633.2837801426523 - ] - }, - { - "num_workers": 4, - "num_warmup": 1, - "images_per_second_mean": 1258.9206679021615, - "images_per_second_std": 0.6530444296624627, - "images_per_second_p50": 1258.989665265139, - "images_per_second_p90": 1259.5449305274076, - "images_per_second_p99": 1259.669865211418, - "us_per_image_mean": 794.331441688836, - "us_per_image_p50": 794.2876955939137, - "us_per_image_p90": 794.7428061361231, - "us_per_image_p99": 794.8452060081202, - "raw_times_s": [ - 39.713590492000094, - 39.69170843500024, - 39.742034332 - ], - "raw_throughput_ips": [ - 1258.989665265139, - 1259.6837468429746, - 1258.0885915983713 - ] - }, - { - "num_workers": 8, - "num_warmup": 1, - "images_per_second_mean": 2396.6259315465195, - "images_per_second_std": 3.531830395243404, - "images_per_second_p50": 2396.544663160484, - "images_per_second_p90": 2400.1022000674607, - "images_per_second_p99": 2400.9026458715307, - "us_per_image_mean": 417.2541732234615, - "us_per_image_p50": 417.26741644832646, - "us_per_image_p90": 417.8538956979106, - "us_per_image_p99": 417.98585352906707, - "raw_times_s": [ - 20.824312890999863, - 20.862953554999876, - 20.899607774999822 - ], - "raw_throughput_ips": [ - 2400.991584294205, - 2396.544663160484, - 2392.3415471848693 - ] - } - ], - "num_images": 49999, - "num_images_total": 50000, - "num_images_decoded": 49999, - "num_images_skipped": 1, - "skip_rate": 2e-05, - "skip_indices": [ - 19876 - ], - "skip_examples": [ - "idx=19876: DecodeError: jpegli decode failed: ./lib/jpegli/color_transform.cc:549: Unsupported color transform 4 -> 2" - ], - "num_runs": 3 -} \ No newline at end of file diff --git a/output/linux_Neoverse-V2/imagecodecs_1t_results.json b/output/linux_Neoverse-V2/imagecodecs_1t_results.json deleted file mode 100644 index 16da161..0000000 --- a/output/linux_Neoverse-V2/imagecodecs_1t_results.json +++ /dev/null @@ -1,53 +0,0 @@ -{ - "library": "imagecodecs", - "mode": "memory", - "run_tag": "1t", - "requested_threads": 1, - "effective_threads": 1, - "system_info": { - "Python": "3.12.3", - "OS": "Linux", - "OS Version": "#12~24.04.1-Ubuntu SMP Fri Mar 27 23:33:21 UTC 2026", - "Machine": "aarch64", - "CPU": { - "brand_raw": "Neoverse-V2", - "arch": "ARM_8", - "hz_advertised_raw": "Unknown", - "count": 16 - }, - "imagecodecs": "2026.3.6" - }, - "benchmark_results": { - "images_per_second_mean": 661.0664986343742, - "images_per_second_std": 0.3087933103815412, - "images_per_second_p50": 661.1710001779237, - "images_per_second_p90": 661.3216285489756, - "images_per_second_p99": 661.3299257683942, - "us_per_image_mean": 1512.707451328, - "us_per_image_p50": 1512.4680298000005, - "us_per_image_p90": 1513.5319222, - "us_per_image_p99": 1513.9859401599997, - "raw_times_s": [ - 75.62340149000002, - 75.60775785699997, - 75.70181932999998, - 75.60512287499995, - 75.63876128000004 - ], - "raw_throughput_ips": [ - 661.1710001779237, - 661.3077998499444, - 660.4861077649878, - 661.3308476816629, - 661.0367376973519 - ], - "num_images_total": 50000, - "num_images_decoded": 50000, - "num_images_skipped": 0, - "skip_rate": 0.0, - "skip_indices": [], - "skip_examples": [] - }, - "num_images": 50000, - "num_runs": 5 -} \ No newline at end of file diff --git a/output/linux_Neoverse-V2/imagecodecs_dataloader_results.json b/output/linux_Neoverse-V2/imagecodecs_dataloader_results.json deleted file mode 100644 index f4d8996..0000000 --- a/output/linux_Neoverse-V2/imagecodecs_dataloader_results.json +++ /dev/null @@ -1,121 +0,0 @@ -{ - "library": "imagecodecs", - "benchmark_type": "dataloader", - "batch_size": 32, - "num_threads": 1, - "system_info": { - "Python": "3.12.3", - "OS": "Linux", - "OS Version": "#12~24.04.1-Ubuntu SMP Fri Mar 27 23:33:21 UTC 2026", - "Machine": "aarch64", - "CPU": { - "brand_raw": "Neoverse-V2", - "arch": "ARM_8", - "hz_advertised_raw": "Unknown", - "count": 16 - }, - "imagecodecs": "2026.3.6" - }, - "worker_results": [ - { - "num_workers": 0, - "num_warmup": 1, - "images_per_second_mean": 656.0309070821394, - "images_per_second_std": 0.16380436183909122, - "images_per_second_p50": 656.1133214823784, - "images_per_second_p90": 656.1644187692704, - "images_per_second_p99": 656.175915658821, - "us_per_image_mean": 1524.3185215199999, - "us_per_image_p50": 1524.1269568200005, - "us_per_image_p90": 1524.705397652, - "us_per_image_p99": 1524.8355468392, - "raw_times_s": [ - 76.20634784100002, - 76.19892999399997, - 76.242500393 - ], - "raw_throughput_ips": [ - 656.1133214823784, - 656.1771930909933, - 655.8022066730463 - ] - }, - { - "num_workers": 2, - "num_warmup": 1, - "images_per_second_mean": 1191.456610197124, - "images_per_second_std": 0.518580483157316, - "images_per_second_p50": 1191.3340632424074, - "images_per_second_p90": 1191.9820788337934, - "images_per_second_p99": 1192.1278823418552, - "us_per_image_mean": 839.3089440733335, - "us_per_image_p50": 839.3951208600038, - "us_per_image_p90": 839.6445678679984, - "us_per_image_p99": 839.7006934447971, - "raw_times_s": [ - 41.98534648099985, - 41.96975604300019, - 41.941239086999985 - ], - "raw_throughput_ips": [ - 1190.8916846173252, - 1191.3340632424074, - 1192.1440827316399 - ] - }, - { - "num_workers": 4, - "num_warmup": 1, - "images_per_second_mean": 2197.6236266876435, - "images_per_second_std": 4.679827673380317, - "images_per_second_p50": 2200.880322417094, - "images_per_second_p90": 2200.9640093976464, - "images_per_second_p99": 2200.9828389682702, - "us_per_image_mean": 455.03903828666654, - "us_per_image_p50": 454.3636424999977, - "us_per_image_p90": 456.00186843600113, - "us_per_image_p99": 456.3704692716019, - "raw_times_s": [ - 22.717102372, - 22.718182124999885, - 22.8205712460001 - ], - "raw_throughput_ips": [ - 2200.9849311427843, - 2200.880322417094, - 2191.005626503053 - ] - }, - { - "num_workers": 8, - "num_warmup": 1, - "images_per_second_mean": 2242.203319778832, - "images_per_second_std": 17.14218782637804, - "images_per_second_p50": 2254.1126023516367, - "images_per_second_p90": 2254.450938096416, - "images_per_second_p99": 2254.527063638991, - "us_per_image_mean": 446.0160958266685, - "us_per_image_p50": 443.6335607000001, - "us_per_image_p90": 449.4182206200021, - "us_per_image_p99": 450.71976910200254, - "raw_times_s": [ - 22.54321928000013, - 22.181678035000004, - 22.177517059000138 - ], - "raw_throughput_ips": [ - 2217.961834952249, - 2254.1126023516367, - 2254.5355220326105 - ] - } - ], - "num_images": 50000, - "num_images_total": 50000, - "num_images_decoded": 50000, - "num_images_skipped": 0, - "skip_rate": 0.0, - "skip_indices": [], - "skip_examples": [], - "num_runs": 3 -} \ No newline at end of file diff --git a/output/linux_Neoverse-V2/imageio_1t_results.json b/output/linux_Neoverse-V2/imageio_1t_results.json deleted file mode 100644 index 41d05cd..0000000 --- a/output/linux_Neoverse-V2/imageio_1t_results.json +++ /dev/null @@ -1,53 +0,0 @@ -{ - "library": "imageio", - "mode": "memory", - "run_tag": "1t", - "requested_threads": 1, - "effective_threads": 1, - "system_info": { - "Python": "3.12.3", - "OS": "Linux", - "OS Version": "#12~24.04.1-Ubuntu SMP Fri Mar 27 23:33:21 UTC 2026", - "Machine": "aarch64", - "CPU": { - "brand_raw": "Neoverse-V2", - "arch": "ARM_8", - "hz_advertised_raw": "Unknown", - "count": 16 - }, - "imageio": "2.37.3" - }, - "benchmark_results": { - "images_per_second_mean": 505.8816451816045, - "images_per_second_std": 1.1802942934624467, - "images_per_second_p50": 505.98557649513526, - "images_per_second_p90": 507.1314687254217, - "images_per_second_p99": 507.2573530224016, - "us_per_image_mean": 1976.757721551999, - "us_per_image_p50": 1976.3409204800016, - "us_per_image_p90": 1982.187577035999, - "us_per_image_p99": 1983.8983289536, - "raw_times_s": [ - 99.20442062500001, - 98.81704602400009, - 98.9668161919999, - 98.56657776800012, - 98.63456977899978 - ], - "raw_throughput_ips": [ - 504.00979800087407, - 505.98557649513526, - 505.2198496817139, - 507.2713401665105, - 506.92166156378846 - ], - "num_images_total": 50000, - "num_images_decoded": 50000, - "num_images_skipped": 0, - "skip_rate": 0.0, - "skip_indices": [], - "skip_examples": [] - }, - "num_images": 50000, - "num_runs": 5 -} \ No newline at end of file diff --git a/output/linux_Neoverse-V2/imageio_dataloader_results.json b/output/linux_Neoverse-V2/imageio_dataloader_results.json deleted file mode 100644 index 6ea6325..0000000 --- a/output/linux_Neoverse-V2/imageio_dataloader_results.json +++ /dev/null @@ -1,121 +0,0 @@ -{ - "library": "imageio", - "benchmark_type": "dataloader", - "batch_size": 32, - "num_threads": 1, - "system_info": { - "Python": "3.12.3", - "OS": "Linux", - "OS Version": "#12~24.04.1-Ubuntu SMP Fri Mar 27 23:33:21 UTC 2026", - "Machine": "aarch64", - "CPU": { - "brand_raw": "Neoverse-V2", - "arch": "ARM_8", - "hz_advertised_raw": "Unknown", - "count": 16 - }, - "imageio": "2.37.3" - }, - "worker_results": [ - { - "num_workers": 0, - "num_warmup": 1, - "images_per_second_mean": 504.14053994530303, - "images_per_second_std": 0.5640159783052274, - "images_per_second_p50": 504.4958533965206, - "images_per_second_p90": 504.5642404221369, - "images_per_second_p99": 504.57962750290056, - "us_per_image_mean": 1983.5763509999986, - "us_per_image_p50": 1982.1768469800008, - "us_per_image_p90": 1985.8043054439986, - "us_per_image_p99": 1986.620483598398, - "raw_times_s": [ - 99.3355585029999, - 99.09205179799983, - 99.10884234900004 - ], - "raw_throughput_ips": [ - 503.3444292608474, - 504.581337178541, - 504.4958533965206 - ] - }, - { - "num_workers": 2, - "num_warmup": 1, - "images_per_second_mean": 931.4997402770035, - "images_per_second_std": 0.6370826137922507, - "images_per_second_p50": 931.4467380226575, - "images_per_second_p90": 932.1334706114611, - "images_per_second_p99": 932.287985443942, - "us_per_image_mean": 1073.5381068666659, - "us_per_image_p50": 1073.5986924200006, - "us_per_image_p90": 1074.2440959880005, - "us_per_image_p99": 1074.3893117908005, - "raw_times_s": [ - 53.67993462100003, - 53.63050906499984, - 53.72027234400002 - ], - "raw_throughput_ips": [ - 931.4467380226575, - 932.3051537586621, - 930.747329049691 - ] - }, - { - "num_workers": 4, - "num_warmup": 1, - "images_per_second_mean": 1764.1155971700302, - "images_per_second_std": 3.301063109040799, - "images_per_second_p50": 1761.8985310445732, - "images_per_second_p90": 1767.4053683286297, - "images_per_second_p99": 1768.6444067175426, - "us_per_image_mean": 566.8582594600017, - "us_per_image_p50": 567.5695747400005, - "us_per_image_p90": 567.6294606760039, - "us_per_image_p99": 567.6429350116047, - "raw_times_s": [ - 28.268038574000002, - 28.378478737000023, - 28.382221608000236 - ], - "raw_throughput_ips": [ - 1768.782077649644, - 1761.8985310445732, - 1761.6661828158742 - ] - }, - { - "num_workers": 8, - "num_warmup": 1, - "images_per_second_mean": 2560.806615610851, - "images_per_second_std": 50.2881860567424, - "images_per_second_p50": 2586.2342076308432, - "images_per_second_p90": 2601.7362645036637, - "images_per_second_p99": 2605.2242273000484, - "us_per_image_mean": 390.65450050666794, - "us_per_image_p50": 386.6625834000024, - "us_per_image_p90": 398.54362898400177, - "us_per_image_p99": 401.2168642404016, - "raw_times_s": [ - 20.07569451900008, - 19.33312917000012, - 19.189351386999988 - ], - "raw_throughput_ips": [ - 2490.5738604798403, - 2586.2342076308432, - 2605.611778721869 - ] - } - ], - "num_images": 50000, - "num_images_total": 50000, - "num_images_decoded": 50000, - "num_images_skipped": 0, - "skip_rate": 0.0, - "skip_indices": [], - "skip_examples": [], - "num_runs": 3 -} \ No newline at end of file diff --git a/output/linux_Neoverse-V2/jpeg4py_1t_results.json b/output/linux_Neoverse-V2/jpeg4py_1t_results.json deleted file mode 100644 index 6fc122a..0000000 --- a/output/linux_Neoverse-V2/jpeg4py_1t_results.json +++ /dev/null @@ -1,57 +0,0 @@ -{ - "library": "jpeg4py", - "mode": "memory", - "run_tag": "1t", - "requested_threads": 1, - "effective_threads": 1, - "system_info": { - "Python": "3.12.3", - "OS": "Linux", - "OS Version": "#12~24.04.1-Ubuntu SMP Fri Mar 27 23:33:21 UTC 2026", - "Machine": "aarch64", - "CPU": { - "brand_raw": "Neoverse-V2", - "arch": "ARM_8", - "hz_advertised_raw": "Unknown", - "count": 16 - }, - "jpeg4py": "0.1.4" - }, - "benchmark_results": { - "images_per_second_mean": 610.8719912278519, - "images_per_second_std": 0.3613605742651273, - "images_per_second_p50": 611.0003671706618, - "images_per_second_p90": 611.155870269885, - "images_per_second_p99": 611.2481094112359, - "us_per_image_mean": 1637.004748158965, - "us_per_image_p50": 1636.660227604552, - "us_per_image_p90": 1638.063577927561, - "us_per_image_p99": 1638.7630142298851, - "raw_times_s": [ - 81.83113750400025, - 81.94039762800003, - 81.83137471999999, - 81.7968365239999, - 81.84325564000028 - ], - "raw_throughput_ips": [ - 611.0021383676334, - 610.1874221673869, - 611.0003671706618, - 611.2583582047193, - 610.9116702288583 - ], - "num_images_total": 50000, - "num_images_decoded": 49999, - "num_images_skipped": 1, - "skip_rate": 2e-05, - "skip_indices": [ - 19876 - ], - "skip_examples": [ - "idx=19876: JPEGRuntimeError: tjDecompress2() failed with error -1 and error string Unsupported color conversion request" - ] - }, - "num_images": 50000, - "num_runs": 5 -} \ No newline at end of file diff --git a/output/linux_Neoverse-V2/jpeg4py_dataloader_results.json b/output/linux_Neoverse-V2/jpeg4py_dataloader_results.json deleted file mode 100644 index 883a897..0000000 --- a/output/linux_Neoverse-V2/jpeg4py_dataloader_results.json +++ /dev/null @@ -1,125 +0,0 @@ -{ - "library": "jpeg4py", - "benchmark_type": "dataloader", - "batch_size": 32, - "num_threads": 1, - "system_info": { - "Python": "3.12.3", - "OS": "Linux", - "OS Version": "#12~24.04.1-Ubuntu SMP Fri Mar 27 23:33:21 UTC 2026", - "Machine": "aarch64", - "CPU": { - "brand_raw": "Neoverse-V2", - "arch": "ARM_8", - "hz_advertised_raw": "Unknown", - "count": 16 - }, - "jpeg4py": "0.1.4" - }, - "worker_results": [ - { - "num_workers": 0, - "num_warmup": 1, - "images_per_second_mean": 599.3562593479392, - "images_per_second_std": 0.15091829046847716, - "images_per_second_p50": 599.3223970718926, - "images_per_second_p90": 599.5090279790821, - "images_per_second_p99": 599.5510199331998, - "us_per_image_mean": 1668.4568615105675, - "us_per_image_p50": 1668.5510251005078, - "us_per_image_p90": 1668.8444224244524, - "us_per_image_p99": 1668.9104368223398, - "raw_times_s": [ - 83.44421967000017, - 83.42588270400029, - 83.39342148200012 - ], - "raw_throughput_ips": [ - 599.1906952660452, - 599.3223970718926, - 599.5556857058795 - ] - }, - { - "num_workers": 2, - "num_warmup": 1, - "images_per_second_mean": 1103.0517165382216, - "images_per_second_std": 2.1758078515304806, - "images_per_second_p50": 1102.2373575336176, - "images_per_second_p90": 1105.2704209033577, - "images_per_second_p99": 1105.9528601615493, - "us_per_image_mean": 906.5793307932844, - "us_per_image_p50": 907.2456065521264, - "us_per_image_p90": 908.134485097703, - "us_per_image_p99": 908.3344827704577, - "raw_times_s": [ - 45.20587991900038, - 45.41692688000012, - 45.36137308199977 - ], - "raw_throughput_ips": [ - 1106.0286867457928, - 1100.8891053352545, - 1102.2373575336176 - ] - }, - { - "num_workers": 4, - "num_warmup": 1, - "images_per_second_mean": 2054.4898093569905, - "images_per_second_std": 13.224345488772565, - "images_per_second_p50": 2046.56044995414, - "images_per_second_p90": 2067.810590934834, - "images_per_second_p99": 2072.59187265549, - "us_per_image_mean": 486.75892577184896, - "us_per_image_p50": 488.6247068941493, - "us_per_image_p90": 489.15538368366737, - "us_per_image_p99": 489.27478596130896, - "raw_times_s": [ - 24.43074672000057, - 24.463913355999466, - 24.117718513 - ], - "raw_throughput_ips": [ - 2046.56044995414, - 2043.7858519368233, - 2073.1231261800076 - ] - }, - { - "num_workers": 8, - "num_warmup": 1, - "images_per_second_mean": 2311.57656306659, - "images_per_second_std": 85.74950909865379, - "images_per_second_p50": 2369.55540348973, - "images_per_second_p90": 2373.773187059764, - "images_per_second_p99": 2374.7221883630214, - "us_per_image_mean": 433.2173421268457, - "us_per_image_p50": 422.02009648192393, - "us_per_image_p90": 449.6430096601956, - "us_per_image_p99": 455.85816512530675, - "raw_times_s": [ - 22.826980349000223, - 21.100582803999714, - 21.053738514000543 - ], - "raw_throughput_ips": [ - 2190.3466527577684, - 2369.55540348973, - 2374.8276329522723 - ] - } - ], - "num_images": 49999, - "num_images_total": 50000, - "num_images_decoded": 49999, - "num_images_skipped": 1, - "skip_rate": 2e-05, - "skip_indices": [ - 19876 - ], - "skip_examples": [ - "idx=19876: JPEGRuntimeError: tjDecompress2() failed with error -1 and error string Unsupported color conversion request" - ], - "num_runs": 3 -} \ No newline at end of file diff --git a/output/linux_Neoverse-V2/kornia_1t_results.json b/output/linux_Neoverse-V2/kornia_1t_results.json deleted file mode 100644 index 54cdc00..0000000 --- a/output/linux_Neoverse-V2/kornia_1t_results.json +++ /dev/null @@ -1,57 +0,0 @@ -{ - "library": "kornia", - "mode": "memory", - "run_tag": "1t", - "requested_threads": 1, - "effective_threads": 1, - "system_info": { - "Python": "3.12.3", - "OS": "Linux", - "OS Version": "#12~24.04.1-Ubuntu SMP Fri Mar 27 23:33:21 UTC 2026", - "Machine": "aarch64", - "CPU": { - "brand_raw": "Neoverse-V2", - "arch": "ARM_8", - "hz_advertised_raw": "Unknown", - "count": 16 - }, - "kornia": "0.1.10" - }, - "benchmark_results": { - "images_per_second_mean": 629.3530026205162, - "images_per_second_std": 0.5411287699883889, - "images_per_second_p50": 629.0748815036729, - "images_per_second_p90": 630.0060402647556, - "images_per_second_p99": 630.0705850820402, - "us_per_image_mean": 1588.9345645232872, - "us_per_image_p50": 1589.6358754775067, - "us_per_image_p90": 1590.2827396987914, - "us_per_image_p99": 1590.627765177696, - "raw_times_s": [ - 79.35369796199939, - 79.48020413799986, - 79.48379515400029, - 79.53171440099959, - 79.37628480300009 - ], - "raw_throughput_ips": [ - 630.0777567284052, - 629.0748815036729, - 629.0464603901545, - 628.6674489110673, - 629.898465569281 - ], - "num_images_total": 50000, - "num_images_decoded": 49999, - "num_images_skipped": 1, - "skip_rate": 2e-05, - "skip_indices": [ - 19876 - ], - "skip_examples": [ - "idx=19876: ValueError: Unsupported number of channels: 4" - ] - }, - "num_images": 50000, - "num_runs": 5 -} \ No newline at end of file diff --git a/output/linux_Neoverse-V2/kornia_dataloader_results.json b/output/linux_Neoverse-V2/kornia_dataloader_results.json deleted file mode 100644 index 83ec6eb..0000000 --- a/output/linux_Neoverse-V2/kornia_dataloader_results.json +++ /dev/null @@ -1,125 +0,0 @@ -{ - "library": "kornia", - "benchmark_type": "dataloader", - "batch_size": 32, - "num_threads": 1, - "system_info": { - "Python": "3.12.3", - "OS": "Linux", - "OS Version": "#12~24.04.1-Ubuntu SMP Fri Mar 27 23:33:21 UTC 2026", - "Machine": "aarch64", - "CPU": { - "brand_raw": "Neoverse-V2", - "arch": "ARM_8", - "hz_advertised_raw": "Unknown", - "count": 16 - }, - "kornia": "0.1.10" - }, - "worker_results": [ - { - "num_workers": 0, - "num_warmup": 1, - "images_per_second_mean": 621.6963946295596, - "images_per_second_std": 0.38345700215850814, - "images_per_second_p50": 621.8385652301881, - "images_per_second_p90": 622.0305287255758, - "images_per_second_p99": 622.0737205120381, - "us_per_image_mean": 1608.5027823689813, - "us_per_image_p50": 1608.1344193083726, - "us_per_image_p90": 1609.51473525071, - "us_per_image_p99": 1609.8253063377358, - "raw_times_s": [ - 80.49138085200047, - 80.37409816400032, - 80.40511283099931 - ], - "raw_throughput_ips": [ - 621.1720990590679, - 622.0785195994227, - 621.8385652301881 - ] - }, - { - "num_workers": 2, - "num_warmup": 1, - "images_per_second_mean": 1114.3914433177574, - "images_per_second_std": 3.5189381148141283, - "images_per_second_p50": 1113.6879512650535, - "images_per_second_p90": 1117.9453595538862, - "images_per_second_p99": 1118.9032764188735, - "us_per_image_mean": 897.3596918404924, - "us_per_image_p50": 897.9175889117649, - "us_per_image_p90": 899.9948724094421, - "us_per_image_p99": 900.4622611964194, - "raw_times_s": [ - 44.68147101899922, - 45.02480914999978, - 44.894981527999335 - ], - "raw_throughput_ips": [ - 1119.0097116260943, - 1110.4766670621243, - 1113.6879512650535 - ] - }, - { - "num_workers": 4, - "num_warmup": 1, - "images_per_second_mean": 1658.1574513092626, - "images_per_second_std": 23.442945534212754, - "images_per_second_p50": 1669.7980848342086, - "images_per_second_p90": 1677.3362244285959, - "images_per_second_p99": 1679.032305837333, - "us_per_image_mean": 603.2006794535896, - "us_per_image_p50": 598.8748035360745, - "us_per_image_p90": 611.945296673931, - "us_per_image_p99": 614.8861576299487, - "raw_times_s": [ - 30.7600307849998, - 29.775120229000095, - 29.943141302000186 - ], - "raw_throughput_ips": [ - 1625.4535097663859, - 1679.2207593271928, - 1669.7980848342086 - ] - }, - { - "num_workers": 8, - "num_warmup": 1, - "images_per_second_mean": 1951.2646168150059, - "images_per_second_std": 28.960723914610494, - "images_per_second_p50": 1971.5758573863359, - "images_per_second_p90": 1971.8428287526476, - "images_per_second_p99": 1971.9028973100678, - "us_per_image_mean": 512.6022697853912, - "us_per_image_p50": 507.20848312966893, - "us_per_image_p90": 520.2222406528058, - "us_per_image_p99": 523.1503360955116, - "raw_times_s": [ - 25.35991694800032, - 26.173260525999467, - 25.355625186999532 - ], - "raw_throughput_ips": [ - 1971.5758573863359, - 1910.308421464456, - 1971.9095715942256 - ] - } - ], - "num_images": 49999, - "num_images_total": 50000, - "num_images_decoded": 49999, - "num_images_skipped": 1, - "skip_rate": 2e-05, - "skip_indices": [ - 19876 - ], - "skip_examples": [ - "idx=19876: ValueError: Unsupported number of channels: 4" - ], - "num_runs": 3 -} \ No newline at end of file diff --git a/output/linux_Neoverse-V2/opencv_1t_results.json b/output/linux_Neoverse-V2/opencv_1t_results.json deleted file mode 100644 index 521433c..0000000 --- a/output/linux_Neoverse-V2/opencv_1t_results.json +++ /dev/null @@ -1,53 +0,0 @@ -{ - "library": "opencv", - "mode": "memory", - "run_tag": "1t", - "requested_threads": 1, - "effective_threads": 1, - "system_info": { - "Python": "3.12.3", - "OS": "Linux", - "OS Version": "#12~24.04.1-Ubuntu SMP Fri Mar 27 23:33:21 UTC 2026", - "Machine": "aarch64", - "CPU": { - "brand_raw": "Neoverse-V2", - "arch": "ARM_8", - "hz_advertised_raw": "Unknown", - "count": 16 - }, - "opencv": "4.13.0.92" - }, - "benchmark_results": { - "images_per_second_mean": 645.4921501411143, - "images_per_second_std": 0.1830192485243782, - "images_per_second_p50": 645.4827667679631, - "images_per_second_p90": 645.7007433760673, - "images_per_second_p99": 645.7978504760301, - "us_per_image_mean": 1549.2056412440033, - "us_per_image_p50": 1549.2280374999973, - "us_per_image_p90": 1549.6470255040076, - "us_per_image_p99": 1549.70734531841, - "raw_times_s": [ - 77.47732462400018, - 77.4546663860001, - 77.48570237600052, - 77.42231505000018, - 77.46140187499986 - ], - "raw_throughput_ips": [ - 645.3501104052253, - 645.5388982094626, - 645.2803351691163, - 645.8086401538038, - 645.4827667679631 - ], - "num_images_total": 50000, - "num_images_decoded": 50000, - "num_images_skipped": 0, - "skip_rate": 0.0, - "skip_indices": [], - "skip_examples": [] - }, - "num_images": 50000, - "num_runs": 5 -} \ No newline at end of file diff --git a/output/linux_Neoverse-V2/opencv_dataloader_results.json b/output/linux_Neoverse-V2/opencv_dataloader_results.json deleted file mode 100644 index ab61b23..0000000 --- a/output/linux_Neoverse-V2/opencv_dataloader_results.json +++ /dev/null @@ -1,121 +0,0 @@ -{ - "library": "opencv", - "benchmark_type": "dataloader", - "batch_size": 32, - "num_threads": 16, - "system_info": { - "Python": "3.12.3", - "OS": "Linux", - "OS Version": "#12~24.04.1-Ubuntu SMP Fri Mar 27 23:33:21 UTC 2026", - "Machine": "aarch64", - "CPU": { - "brand_raw": "Neoverse-V2", - "arch": "ARM_8", - "hz_advertised_raw": "Unknown", - "count": 16 - }, - "opencv": "4.13.0.92" - }, - "worker_results": [ - { - "num_workers": 0, - "num_warmup": 1, - "images_per_second_mean": 638.8271958957081, - "images_per_second_std": 0.2943509012434076, - "images_per_second_p50": 638.7229781475853, - "images_per_second_p90": 639.1272585568005, - "images_per_second_p99": 639.2182216488739, - "us_per_image_mean": 1565.3688801666674, - "us_per_image_p50": 1565.6239625200033, - "us_per_image_p90": 1566.0019448559979, - "us_per_image_p99": 1566.0869908815966, - "raw_times_s": [ - 78.28119812600016, - 78.30482202199983, - 78.21931187700011 - ], - "raw_throughput_ips": [ - 638.7229781475853, - 638.5302808804347, - 639.2283286591042 - ] - }, - { - "num_workers": 2, - "num_warmup": 1, - "images_per_second_mean": 1148.1269255043555, - "images_per_second_std": 2.036117817525007, - "images_per_second_p50": 1149.3215686147894, - "images_per_second_p90": 1149.703183401264, - "images_per_second_p99": 1149.7890467282207, - "us_per_image_mean": 870.9865839933263, - "us_per_image_p50": 870.0785118000022, - "us_per_image_p90": 872.5466615759906, - "us_per_image_p99": 873.101995275588, - "raw_times_s": [ - 43.50392559000011, - 43.48587705799946, - 43.658184950999384 - ], - "raw_throughput_ips": [ - 1149.3215686147894, - 1149.7985870978825, - 1145.2606208003947 - ] - }, - { - "num_workers": 4, - "num_warmup": 1, - "images_per_second_mean": 2182.330544199316, - "images_per_second_std": 42.56823194540327, - "images_per_second_p50": 2206.4157737148253, - "images_per_second_p90": 2215.7381633873338, - "images_per_second_p99": 2217.8357010636482, - "us_per_image_mean": 458.40244395333985, - "us_per_image_p50": 453.22373594000965, - "us_per_image_p90": 467.5575032040142, - "us_per_image_p99": 470.78260083841525, - "raw_times_s": [ - 22.54213254499973, - 22.661186797000482, - 23.557047251000768 - ], - "raw_throughput_ips": [ - 2218.068760805461, - 2206.4157737148253, - 2122.507098077662 - ] - }, - { - "num_workers": 8, - "num_warmup": 1, - "images_per_second_mean": 2413.8276450598883, - "images_per_second_std": 62.67760237696107, - "images_per_second_p50": 2421.2050349109672, - "images_per_second_p90": 2473.55035450767, - "images_per_second_p99": 2485.3280514169282, - "us_per_image_mean": 414.56066208000266, - "us_per_image_p50": 413.0174791399986, - "us_per_image_p90": 425.41540477200033, - "us_per_image_p99": 428.2049380392007, - "raw_times_s": [ - 20.65087395699993, - 20.10748104600043, - 21.425744309000038 - ], - "raw_throughput_ips": [ - 2421.2050349109672, - 2486.6366844068457, - 2333.641215861852 - ] - } - ], - "num_images": 50000, - "num_images_total": 50000, - "num_images_decoded": 50000, - "num_images_skipped": 0, - "skip_rate": 0.0, - "skip_indices": [], - "skip_examples": [], - "num_runs": 3 -} \ No newline at end of file diff --git a/output/linux_Neoverse-V2/opencv_default_results.json b/output/linux_Neoverse-V2/opencv_default_results.json deleted file mode 100644 index c49ab31..0000000 --- a/output/linux_Neoverse-V2/opencv_default_results.json +++ /dev/null @@ -1,53 +0,0 @@ -{ - "library": "opencv", - "mode": "memory", - "run_tag": "default", - "requested_threads": 0, - "effective_threads": 16, - "system_info": { - "Python": "3.12.3", - "OS": "Linux", - "OS Version": "#12~24.04.1-Ubuntu SMP Fri Mar 27 23:33:21 UTC 2026", - "Machine": "aarch64", - "CPU": { - "brand_raw": "Neoverse-V2", - "arch": "ARM_8", - "hz_advertised_raw": "Unknown", - "count": 16 - }, - "opencv": "4.13.0.92" - }, - "benchmark_results": { - "images_per_second_mean": 645.9702081196797, - "images_per_second_std": 0.24962217597245026, - "images_per_second_p50": 646.1592329909106, - "images_per_second_p90": 646.1827638636378, - "images_per_second_p99": 646.1884107475544, - "us_per_image_mean": 1548.0592398560002, - "us_per_image_p50": 1547.6061455800118, - "us_per_image_p90": 1548.792633711997, - "us_per_image_p99": 1548.7967841592013, - "raw_times_s": [ - 77.38030727900059, - 77.3767381460002, - 77.4392858149995, - 77.43986226600009, - 77.37861645799967 - ], - "raw_throughput_ips": [ - 646.1592329909106, - 646.1890381791006, - 645.6671116447114, - 645.6623053932324, - 646.1733523904436 - ], - "num_images_total": 50000, - "num_images_decoded": 50000, - "num_images_skipped": 0, - "skip_rate": 0.0, - "skip_indices": [], - "skip_examples": [] - }, - "num_images": 50000, - "num_runs": 5 -} \ No newline at end of file diff --git a/output/linux_Neoverse-V2/pillow_1t_results.json b/output/linux_Neoverse-V2/pillow_1t_results.json deleted file mode 100644 index 039dda8..0000000 --- a/output/linux_Neoverse-V2/pillow_1t_results.json +++ /dev/null @@ -1,53 +0,0 @@ -{ - "library": "pillow", - "mode": "memory", - "run_tag": "1t", - "requested_threads": 1, - "effective_threads": 1, - "system_info": { - "Python": "3.12.3", - "OS": "Linux", - "OS Version": "#12~24.04.1-Ubuntu SMP Fri Mar 27 23:33:21 UTC 2026", - "Machine": "aarch64", - "CPU": { - "brand_raw": "Neoverse-V2", - "arch": "ARM_8", - "hz_advertised_raw": "Unknown", - "count": 16 - }, - "pillow": "12.2.0" - }, - "benchmark_results": { - "images_per_second_mean": 550.5241871495566, - "images_per_second_std": 0.6445298407372693, - "images_per_second_p50": 550.5717401918282, - "images_per_second_p90": 551.1903601527162, - "images_per_second_p99": 551.2723378832222, - "us_per_image_mean": 1816.4531092800062, - "us_per_image_p50": 1816.2937306800086, - "us_per_image_p90": 1818.9246292600074, - "us_per_image_p99": 1819.8930172720072, - "raw_times_s": [ - 90.86553246200037, - 91.00003079700036, - 90.81468653400043, - 90.69777391499974, - 90.73525361200063 - ], - "raw_throughput_ips": [ - 550.2636549332918, - 549.4503635008457, - 550.5717401918282, - 551.2814465199451, - 551.0537306018728 - ], - "num_images_total": 50000, - "num_images_decoded": 50000, - "num_images_skipped": 0, - "skip_rate": 0.0, - "skip_indices": [], - "skip_examples": [] - }, - "num_images": 50000, - "num_runs": 5 -} \ No newline at end of file diff --git a/output/linux_Neoverse-V2/pillow_dataloader_results.json b/output/linux_Neoverse-V2/pillow_dataloader_results.json deleted file mode 100644 index ddc0c90..0000000 --- a/output/linux_Neoverse-V2/pillow_dataloader_results.json +++ /dev/null @@ -1,121 +0,0 @@ -{ - "library": "pillow", - "benchmark_type": "dataloader", - "batch_size": 32, - "num_threads": 1, - "system_info": { - "Python": "3.12.3", - "OS": "Linux", - "OS Version": "#12~24.04.1-Ubuntu SMP Fri Mar 27 23:33:21 UTC 2026", - "Machine": "aarch64", - "CPU": { - "brand_raw": "Neoverse-V2", - "arch": "ARM_8", - "hz_advertised_raw": "Unknown", - "count": 16 - }, - "pillow": "12.2.0" - }, - "worker_results": [ - { - "num_workers": 0, - "num_warmup": 1, - "images_per_second_mean": 542.7678931600163, - "images_per_second_std": 0.8024914471288525, - "images_per_second_p50": 543.1829033060797, - "images_per_second_p90": 543.4167113133385, - "images_per_second_p99": 543.4693181149717, - "us_per_image_mean": 1842.4121999133301, - "us_per_image_p50": 1841.0005063000062, - "us_per_image_p90": 1845.1805862999972, - "us_per_image_p99": 1846.1211042999953, - "raw_times_s": [ - 92.31128031499975, - 92.05002531500031, - 92.00052435699945 - ], - "raw_throughput_ips": [ - 541.6456128588161, - 543.1829033060797, - 543.4751633151532 - ] - }, - { - "num_workers": 2, - "num_warmup": 1, - "images_per_second_mean": 984.9712249320395, - "images_per_second_std": 1.8140042012519724, - "images_per_second_p50": 984.7427649958597, - "images_per_second_p90": 986.7872089008714, - "images_per_second_p99": 987.247208779499, - "us_per_image_mean": 1015.2615277799972, - "us_per_image_p50": 1015.4936248800005, - "us_per_image_p90": 1017.0394210560115, - "us_per_image_p99": 1017.3872251956141, - "raw_times_s": [ - 50.64325441799883, - 50.77468124400002, - 50.87129350500072 - ], - "raw_throughput_ips": [ - 987.2983198771243, - 984.7427649958597, - 982.8725899231347 - ] - }, - { - "num_workers": 4, - "num_warmup": 1, - "images_per_second_mean": 1925.4939668463865, - "images_per_second_std": 2.809236738836973, - "images_per_second_p50": 1927.4141372968359, - "images_per_second_p90": 1927.5195701614723, - "images_per_second_p99": 1927.5432925560156, - "us_per_image_mean": 519.3483584000084, - "us_per_image_p50": 518.8298563600256, - "us_per_image_p90": 520.1026402000134, - "us_per_image_p99": 520.3890165640107, - "raw_times_s": [ - 25.94149281800128, - 26.02104180800052, - 25.93971913399946 - ], - "raw_throughput_ips": [ - 1927.4141372968359, - 1921.5218348646913, - 1927.5459283776315 - ] - }, - { - "num_workers": 8, - "num_warmup": 1, - "images_per_second_mean": 2349.6269895398345, - "images_per_second_std": 1.520876950567673, - "images_per_second_p50": 2350.19687756702, - "images_per_second_p90": 2350.9499020820163, - "images_per_second_p99": 2351.11933259789, - "us_per_image_mean": 425.59964776000317, - "us_per_image_p50": 425.49626780000835, - "us_per_image_p90": 425.8806580080054, - "us_per_image_p99": 425.9671458048048, - "raw_times_s": [ - 21.266295995999826, - 21.274813390000418, - 21.298837778000234 - ], - "raw_throughput_ips": [ - 2351.138158210765, - 2350.19687756702, - 2347.5459328417187 - ] - } - ], - "num_images": 50000, - "num_images_total": 50000, - "num_images_decoded": 50000, - "num_images_skipped": 0, - "skip_rate": 0.0, - "skip_indices": [], - "skip_examples": [], - "num_runs": 3 -} \ No newline at end of file diff --git a/output/linux_Neoverse-V2/pyvips_1t_results.json b/output/linux_Neoverse-V2/pyvips_1t_results.json deleted file mode 100644 index a6caf31..0000000 --- a/output/linux_Neoverse-V2/pyvips_1t_results.json +++ /dev/null @@ -1,53 +0,0 @@ -{ - "library": "pyvips", - "mode": "memory", - "run_tag": "1t", - "requested_threads": 1, - "effective_threads": 1, - "system_info": { - "Python": "3.12.3", - "OS": "Linux", - "OS Version": "#12~24.04.1-Ubuntu SMP Fri Mar 27 23:33:21 UTC 2026", - "Machine": "aarch64", - "CPU": { - "brand_raw": "Neoverse-V2", - "arch": "ARM_8", - "hz_advertised_raw": "Unknown", - "count": 16 - }, - "pyvips": "3.1.1" - }, - "benchmark_results": { - "images_per_second_mean": 412.80920004123516, - "images_per_second_std": 0.8457649126220921, - "images_per_second_p50": 413.05035260329396, - "images_per_second_p90": 413.5736722617966, - "images_per_second_p99": 413.8490450019272, - "us_per_image_mean": 2422.4368149719885, - "us_per_image_p50": 2421.012338319997, - "us_per_image_p90": 2428.0089009319927, - "us_per_image_p99": 2430.9655135832027, - "raw_times_s": [ - 120.80806816599943, - 121.15406065899879, - 121.05061691599985, - 121.03175669699885, - 121.5647013050002 - ], - "raw_throughput_ips": [ - 413.8796419730528, - 412.69768200944094, - 413.05035260329396, - 413.1147176949124, - 411.303605925476 - ], - "num_images_total": 50000, - "num_images_decoded": 50000, - "num_images_skipped": 0, - "skip_rate": 0.0, - "skip_indices": [], - "skip_examples": [] - }, - "num_images": 50000, - "num_runs": 5 -} \ No newline at end of file diff --git a/output/linux_Neoverse-V2/pyvips_default_results.json b/output/linux_Neoverse-V2/pyvips_default_results.json deleted file mode 100644 index acd21bb..0000000 --- a/output/linux_Neoverse-V2/pyvips_default_results.json +++ /dev/null @@ -1,53 +0,0 @@ -{ - "library": "pyvips", - "mode": "memory", - "run_tag": "default", - "requested_threads": 0, - "effective_threads": 16, - "system_info": { - "Python": "3.12.3", - "OS": "Linux", - "OS Version": "#12~24.04.1-Ubuntu SMP Fri Mar 27 23:33:21 UTC 2026", - "Machine": "aarch64", - "CPU": { - "brand_raw": "Neoverse-V2", - "arch": "ARM_8", - "hz_advertised_raw": "Unknown", - "count": 16 - }, - "pyvips": "3.1.1" - }, - "benchmark_results": { - "images_per_second_mean": 388.97880363002923, - "images_per_second_std": 1.025060270865241, - "images_per_second_p50": 389.52806021517824, - "images_per_second_p90": 389.84694036997774, - "images_per_second_p99": 389.9520986083787, - "us_per_image_mean": 2570.852065500003, - "us_per_image_p50": 2567.2091490599996, - "us_per_image_p90": 2579.1739944159926, - "us_per_image_p99": 2582.59488626558, - "raw_times_s": [ - 128.21703501200136, - 128.31314924199978, - 128.36045745299998, - 128.67362540000067, - 129.14874926799894 - ], - "raw_throughput_ips": [ - 389.96378285708994, - 389.6716766393095, - 389.52806021517824, - 388.58002053309474, - 387.1504779054738 - ], - "num_images_total": 50000, - "num_images_decoded": 50000, - "num_images_skipped": 0, - "skip_rate": 0.0, - "skip_indices": [], - "skip_examples": [] - }, - "num_images": 50000, - "num_runs": 5 -} \ No newline at end of file diff --git a/output/linux_Neoverse-V2/run_summary.json b/output/linux_Neoverse-V2/run_summary.json deleted file mode 100644 index 01a26e3..0000000 --- a/output/linux_Neoverse-V2/run_summary.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "timestamp_utc": "2026-05-20T08:38:10.022642+00:00", - "system": "linux_Neoverse-V2", - "mode": "both", - "num_images": 50000, - "num_runs": 5, - "dataloader_runs": 3, - "workers": [ - 0, - 2, - 4, - 8 - ], - "libs_requested": [ - "ajpegli" - ], - "libs_skipped_platform": [], - "libs_run": [ - "ajpegli" - ], - "failures": [], - "exit_status": "ok" -} \ No newline at end of file diff --git a/output/linux_Neoverse-V2/simplejpeg_1t_results.json b/output/linux_Neoverse-V2/simplejpeg_1t_results.json deleted file mode 100644 index 97b5453..0000000 --- a/output/linux_Neoverse-V2/simplejpeg_1t_results.json +++ /dev/null @@ -1,53 +0,0 @@ -{ - "library": "simplejpeg", - "mode": "memory", - "run_tag": "1t", - "requested_threads": 1, - "effective_threads": 1, - "system_info": { - "Python": "3.12.3", - "OS": "Linux", - "OS Version": "#12~24.04.1-Ubuntu SMP Fri Mar 27 23:33:21 UTC 2026", - "Machine": "aarch64", - "CPU": { - "brand_raw": "Neoverse-V2", - "arch": "ARM_8", - "hz_advertised_raw": "Unknown", - "count": 16 - }, - "simplejpeg": "1.9.0" - }, - "benchmark_results": { - "images_per_second_mean": 662.1853331604718, - "images_per_second_std": 0.2511551973076927, - "images_per_second_p50": 662.1999258953365, - "images_per_second_p90": 662.4182759557799, - "images_per_second_p99": 662.4340360061295, - "us_per_image_mean": 1510.1514543239973, - "us_per_image_p50": 1510.1179581799806, - "us_per_image_p90": 1510.7907764920164, - "us_per_image_p99": 1511.15539465122, - "raw_times_s": [ - 75.50915397800054, - 75.50589790899903, - 75.4840023809993, - 75.47901392399945, - 75.55979538900101 - ], - "raw_throughput_ips": [ - 662.1713708323022, - 662.1999258953365, - 662.392009205197, - 662.4357871228351, - 661.7275727466877 - ], - "num_images_total": 50000, - "num_images_decoded": 50000, - "num_images_skipped": 0, - "skip_rate": 0.0, - "skip_indices": [], - "skip_examples": [] - }, - "num_images": 50000, - "num_runs": 5 -} \ No newline at end of file diff --git a/output/linux_Neoverse-V2/simplejpeg_dataloader_results.json b/output/linux_Neoverse-V2/simplejpeg_dataloader_results.json deleted file mode 100644 index 3bc8fdb..0000000 --- a/output/linux_Neoverse-V2/simplejpeg_dataloader_results.json +++ /dev/null @@ -1,121 +0,0 @@ -{ - "library": "simplejpeg", - "benchmark_type": "dataloader", - "batch_size": 32, - "num_threads": 1, - "system_info": { - "Python": "3.12.3", - "OS": "Linux", - "OS Version": "#12~24.04.1-Ubuntu SMP Fri Mar 27 23:33:21 UTC 2026", - "Machine": "aarch64", - "CPU": { - "brand_raw": "Neoverse-V2", - "arch": "ARM_8", - "hz_advertised_raw": "Unknown", - "count": 16 - }, - "simplejpeg": "1.9.0" - }, - "worker_results": [ - { - "num_workers": 0, - "num_warmup": 1, - "images_per_second_mean": 658.2656932671556, - "images_per_second_std": 0.44173287192377425, - "images_per_second_p50": 658.5302144265324, - "images_per_second_p90": 658.6048815242782, - "images_per_second_p99": 658.6216816212709, - "us_per_image_mean": 1519.1441096666797, - "us_per_image_p50": 1518.5332094000114, - "us_per_image_p90": 1520.1715237200187, - "us_per_image_p99": 1520.5401444420204, - "raw_times_s": [ - 75.91590086500037, - 76.02905511500103, - 75.92666047000057 - ], - "raw_throughput_ips": [ - 658.6235482987146, - 657.6433170762197, - 658.5302144265324 - ] - }, - { - "num_workers": 2, - "num_warmup": 1, - "images_per_second_mean": 1178.496799393621, - "images_per_second_std": 2.4234464519101993, - "images_per_second_p50": 1177.3362274667202, - "images_per_second_p90": 1180.9631125038823, - "images_per_second_p99": 1181.7791616372438, - "us_per_image_mean": 848.5421631866726, - "us_per_image_p50": 849.3750355000157, - "us_per_image_p90": 849.9826773880122, - "us_per_image_p99": 850.1193968128115, - "raw_times_s": [ - 42.305843309999545, - 42.46875177500078, - 42.50672939300057 - ], - "raw_throughput_ips": [ - 1181.869833763173, - 1177.3362274667202, - 1176.2843369509703 - ] - }, - { - "num_workers": 4, - "num_warmup": 1, - "images_per_second_mean": 2230.63979222002, - "images_per_second_std": 13.082824733670565, - "images_per_second_p50": 2232.74514891825, - "images_per_second_p90": 2242.95395853087, - "images_per_second_p99": 2245.2509406937097, - "us_per_image_mean": 448.3173177466597, - "us_per_image_p50": 447.8791502400054, - "us_per_image_p90": 450.9669581919952, - "us_per_image_p99": 451.6617149811929, - "raw_times_s": [ - 22.586945508999634, - 22.39395751200027, - 22.26669464099905 - ], - "raw_throughput_ips": [ - 2213.668066807785, - 2232.74514891825, - 2245.506160934025 - ] - }, - { - "num_workers": 8, - "num_warmup": 1, - "images_per_second_mean": 2420.7569232680075, - "images_per_second_std": 22.623038133253196, - "images_per_second_p50": 2432.2356458731992, - "images_per_second_p90": 2439.151380904825, - "images_per_second_p99": 2440.7074212869406, - "us_per_image_mean": 413.1302295466715, - "us_per_image_p50": 411.14437316002295, - "us_per_image_p90": 417.07532195999374, - "us_per_image_p99": 418.40978543998716, - "raw_times_s": [ - 20.48441281600026, - 20.927902957999322, - 20.557218658001148 - ], - "raw_throughput_ips": [ - 2440.8803146627315, - 2389.1548092680914, - 2432.2356458731992 - ] - } - ], - "num_images": 50000, - "num_images_total": 50000, - "num_images_decoded": 50000, - "num_images_skipped": 0, - "skip_rate": 0.0, - "skip_indices": [], - "skip_examples": [], - "num_runs": 3 -} \ No newline at end of file diff --git a/output/linux_Neoverse-V2/skimage_1t_results.json b/output/linux_Neoverse-V2/skimage_1t_results.json deleted file mode 100644 index 0374070..0000000 --- a/output/linux_Neoverse-V2/skimage_1t_results.json +++ /dev/null @@ -1,53 +0,0 @@ -{ - "library": "skimage", - "mode": "memory", - "run_tag": "1t", - "requested_threads": 1, - "effective_threads": 1, - "system_info": { - "Python": "3.12.3", - "OS": "Linux", - "OS Version": "#12~24.04.1-Ubuntu SMP Fri Mar 27 23:33:21 UTC 2026", - "Machine": "aarch64", - "CPU": { - "brand_raw": "Neoverse-V2", - "arch": "ARM_8", - "hz_advertised_raw": "Unknown", - "count": 16 - }, - "skimage": "0.26.0" - }, - "benchmark_results": { - "images_per_second_mean": 499.0374589371987, - "images_per_second_std": 1.2775217800584946, - "images_per_second_p50": 498.536238711058, - "images_per_second_p90": 500.57893696857866, - "images_per_second_p99": 500.6436357277299, - "us_per_image_mean": 2003.8707135160075, - "us_per_image_p50": 2005.8722362600022, - "us_per_image_p90": 2009.039783715998, - "us_per_image_p99": 2009.2479958576005, - "raw_times_s": [ - 99.90586755200093, - 99.87000431300112, - 100.4346381739997, - 100.46355652700004, - 100.2936118130001 - ], - "raw_throughput_ips": [ - 500.47110570332654, - 500.6508244787467, - 497.83621377095665, - 497.6929120219059, - 498.536238711058 - ], - "num_images_total": 50000, - "num_images_decoded": 50000, - "num_images_skipped": 0, - "skip_rate": 0.0, - "skip_indices": [], - "skip_examples": [] - }, - "num_images": 50000, - "num_runs": 5 -} \ No newline at end of file diff --git a/output/linux_Neoverse-V2/skimage_dataloader_results.json b/output/linux_Neoverse-V2/skimage_dataloader_results.json deleted file mode 100644 index 7a1c67a..0000000 --- a/output/linux_Neoverse-V2/skimage_dataloader_results.json +++ /dev/null @@ -1,121 +0,0 @@ -{ - "library": "skimage", - "benchmark_type": "dataloader", - "batch_size": 32, - "num_threads": 1, - "system_info": { - "Python": "3.12.3", - "OS": "Linux", - "OS Version": "#12~24.04.1-Ubuntu SMP Fri Mar 27 23:33:21 UTC 2026", - "Machine": "aarch64", - "CPU": { - "brand_raw": "Neoverse-V2", - "arch": "ARM_8", - "hz_advertised_raw": "Unknown", - "count": 16 - }, - "skimage": "0.26.0" - }, - "worker_results": [ - { - "num_workers": 0, - "num_warmup": 1, - "images_per_second_mean": 496.59341528524413, - "images_per_second_std": 0.42561925047715926, - "images_per_second_p50": 496.6546898198876, - "images_per_second_p90": 496.9960139471752, - "images_per_second_p99": 497.0728118758149, - "us_per_image_mean": 2013.7212937933266, - "us_per_image_p50": 2013.4713725599795, - "us_per_image_p90": 2015.4537466879917, - "us_per_image_p99": 2015.8997808667946, - "raw_times_s": [ - 100.58715843000027, - 100.67356862799897, - 100.79746701099975 - ], - "raw_throughput_ips": [ - 497.0813449789971, - 496.6546898198876, - 496.04421105684764 - ] - }, - { - "num_workers": 2, - "num_warmup": 1, - "images_per_second_mean": 908.453488462815, - "images_per_second_std": 4.758081953470801, - "images_per_second_p50": 908.3130062162343, - "images_per_second_p90": 913.1425179941799, - "images_per_second_p99": 914.2291581442178, - "us_per_image_mean": 1100.8020108133496, - "us_per_image_p50": 1100.9420686000158, - "us_per_image_p90": 1106.421004328011, - "us_per_image_p99": 1107.6537648668098, - "raw_times_s": [ - 54.68366127900117, - 55.04710343000079, - 55.389536913000484 - ], - "raw_throughput_ips": [ - 914.3498959386664, - 908.3130062162343, - 902.6975632335444 - ] - }, - { - "num_workers": 4, - "num_warmup": 1, - "images_per_second_mean": 1704.907419382418, - "images_per_second_std": 2.041634337137562, - "images_per_second_p50": 1704.9578822297094, - "images_per_second_p90": 1706.8974062154875, - "images_per_second_p99": 1707.3337991122878, - "us_per_image_mean": 586.542954013324, - "us_per_image_p50": 586.5247525599989, - "us_per_image_p90": 587.2347067679948, - "us_per_image_p99": 587.3944464647939, - "raw_times_s": [ - 29.326237627999944, - 29.28459570799896, - 29.37060976599969 - ], - "raw_throughput_ips": [ - 1704.9578822297094, - 1707.3822872119322, - 1702.3820887056122 - ] - }, - { - "num_workers": 8, - "num_warmup": 1, - "images_per_second_mean": 2315.2323371450543, - "images_per_second_std": 15.632687194885207, - "images_per_second_p50": 2318.623698687141, - "images_per_second_p90": 2329.689620085236, - "images_per_second_p99": 2332.179452399808, - "us_per_image_mean": 431.941829339994, - "us_per_image_p50": 431.29033855999296, - "us_per_image_p90": 434.9000964159932, - "us_per_image_p99": 435.71229193359324, - "raw_times_s": [ - 21.79012679399966, - 21.564516927999648, - 21.43663067899979 - ], - "raw_throughput_ips": [ - 2294.617212313261, - 2318.623698687141, - 2332.45610043476 - ] - } - ], - "num_images": 50000, - "num_images_total": 50000, - "num_images_decoded": 50000, - "num_images_skipped": 0, - "skip_rate": 0.0, - "skip_indices": [], - "skip_examples": [], - "num_runs": 3 -} \ No newline at end of file diff --git a/output/linux_Neoverse-V2/tensorflow_1t_results.json b/output/linux_Neoverse-V2/tensorflow_1t_results.json deleted file mode 100644 index 534b87d..0000000 --- a/output/linux_Neoverse-V2/tensorflow_1t_results.json +++ /dev/null @@ -1,53 +0,0 @@ -{ - "library": "tensorflow", - "mode": "memory", - "run_tag": "1t", - "requested_threads": 1, - "effective_threads": 1, - "system_info": { - "Python": "3.12.3", - "OS": "Linux", - "OS Version": "#12~24.04.1-Ubuntu SMP Fri Mar 27 23:33:21 UTC 2026", - "Machine": "aarch64", - "CPU": { - "brand_raw": "Neoverse-V2", - "arch": "ARM_8", - "hz_advertised_raw": "Unknown", - "count": 16 - }, - "tensorflow": "2.21.0" - }, - "benchmark_results": { - "images_per_second_mean": 390.7867815119049, - "images_per_second_std": 0.34480256468520487, - "images_per_second_p50": 390.7063850007743, - "images_per_second_p90": 391.1970023439421, - "images_per_second_p99": 391.37374172778505, - "us_per_image_mean": 2558.942178415993, - "us_per_image_p50": 2559.4667463599762, - "us_per_image_p90": 2561.0671240360243, - "us_per_image_p99": 2561.152476565633, - "raw_times_s": [ - 128.0580980090017, - 127.90915406299973, - 128.0462434910005, - 127.74871172299754, - 127.97333731799881 - ], - "raw_throughput_ips": [ - 390.44777938592614, - 390.90243670420375, - 390.4839270315194, - 391.3933794371009, - 390.7063850007743 - ], - "num_images_total": 50000, - "num_images_decoded": 50000, - "num_images_skipped": 0, - "skip_rate": 0.0, - "skip_indices": [], - "skip_examples": [] - }, - "num_images": 50000, - "num_runs": 5 -} \ No newline at end of file diff --git a/output/linux_Neoverse-V2/torchvision_1t_results.json b/output/linux_Neoverse-V2/torchvision_1t_results.json deleted file mode 100644 index 474b155..0000000 --- a/output/linux_Neoverse-V2/torchvision_1t_results.json +++ /dev/null @@ -1,53 +0,0 @@ -{ - "library": "torchvision", - "mode": "memory", - "run_tag": "1t", - "requested_threads": 1, - "effective_threads": 1, - "system_info": { - "Python": "3.12.3", - "OS": "Linux", - "OS Version": "#12~24.04.1-Ubuntu SMP Fri Mar 27 23:33:21 UTC 2026", - "Machine": "aarch64", - "CPU": { - "brand_raw": "Neoverse-V2", - "arch": "ARM_8", - "hz_advertised_raw": "Unknown", - "count": 16 - }, - "torchvision": "0.26.0+cpu" - }, - "benchmark_results": { - "images_per_second_mean": 642.8660351387406, - "images_per_second_std": 0.3918961977359971, - "images_per_second_p50": 642.9969449752326, - "images_per_second_p90": 643.1677392405912, - "images_per_second_p99": 643.2412683666441, - "us_per_image_mean": 1555.5346172240024, - "us_per_image_p50": 1555.2173425, - "us_per_image_p90": 1556.5712594160068, - "us_per_image_p99": 1557.2810190936245, - "raw_times_s": [ - 77.760867125, - 77.76941633099887, - 77.86799406400132, - 77.73034382200058, - 77.75503296399984 - ], - "raw_throughput_ips": [ - 642.9969449752326, - 642.9262602048103, - 642.1123415469514, - 643.2494382695389, - 643.0451906971698 - ], - "num_images_total": 50000, - "num_images_decoded": 50000, - "num_images_skipped": 0, - "skip_rate": 0.0, - "skip_indices": [], - "skip_examples": [] - }, - "num_images": 50000, - "num_runs": 5 -} \ No newline at end of file diff --git a/output/linux_Neoverse-V2/torchvision_dataloader_results.json b/output/linux_Neoverse-V2/torchvision_dataloader_results.json deleted file mode 100644 index eea7160..0000000 --- a/output/linux_Neoverse-V2/torchvision_dataloader_results.json +++ /dev/null @@ -1,121 +0,0 @@ -{ - "library": "torchvision", - "benchmark_type": "dataloader", - "batch_size": 32, - "num_threads": 16, - "system_info": { - "Python": "3.12.3", - "OS": "Linux", - "OS Version": "#12~24.04.1-Ubuntu SMP Fri Mar 27 23:33:21 UTC 2026", - "Machine": "aarch64", - "CPU": { - "brand_raw": "Neoverse-V2", - "arch": "ARM_8", - "hz_advertised_raw": "Unknown", - "count": 16 - }, - "torchvision": "0.26.0+cpu" - }, - "worker_results": [ - { - "num_workers": 0, - "num_warmup": 1, - "images_per_second_mean": 626.189364507698, - "images_per_second_std": 0.7929360206894199, - "images_per_second_p50": 625.8024841025891, - "images_per_second_p90": 626.9959545056324, - "images_per_second_p99": 627.2644853463171, - "us_per_image_mean": 1596.9635687200087, - "us_per_image_p50": 1597.9482750600073, - "us_per_image_p90": 1598.6251858600226, - "us_per_image_p99": 1598.7774907900261, - "raw_times_s": [ - 79.89741375300036, - 79.93972067800132, - 79.7074008769996 - ], - "raw_throughput_ips": [ - 625.8024841025891, - 625.4712873141117, - 627.2943221063932 - ] - }, - { - "num_workers": 2, - "num_warmup": 1, - "images_per_second_mean": 1148.9329384650737, - "images_per_second_std": 4.115400193168514, - "images_per_second_p50": 1148.558381814182, - "images_per_second_p90": 1153.0317431227727, - "images_per_second_p99": 1154.0382494172056, - "us_per_image_mean": 870.3839804466528, - "us_per_image_p50": 870.6566560599822, - "us_per_image_p90": 873.3768059319918, - "us_per_image_p99": 873.988839653194, - "raw_times_s": [ - 43.32192209399909, - 43.53283280299911, - 43.70284216999971 - ], - "raw_throughput_ips": [ - 1154.1500834499204, - 1148.558381814182, - 1144.0903501311188 - ] - }, - { - "num_workers": 4, - "num_warmup": 1, - "images_per_second_mean": 2137.5340886326135, - "images_per_second_std": 3.2415105590655373, - "images_per_second_p50": 2139.189433935258, - "images_per_second_p90": 2140.164744475292, - "images_per_second_p99": 2140.3841893467998, - "us_per_image_mean": 467.8298733199942, - "us_per_image_p50": 467.46678163999604, - "us_per_image_p90": 468.5512101999848, - "us_per_image_p99": 468.79520662598225, - "raw_times_s": [ - 23.373339081999802, - 23.360026049000226, - 23.441115866999098 - ], - "raw_throughput_ips": [ - 2139.189433935258, - 2140.4085721103006, - 2133.0042598522823 - ] - }, - { - "num_workers": 8, - "num_warmup": 1, - "images_per_second_mean": 2557.3201989609065, - "images_per_second_std": 149.62549571570372, - "images_per_second_p50": 2652.3437205858145, - "images_per_second_p90": 2669.3045475055583, - "images_per_second_p99": 2673.1207335625004, - "us_per_image_mean": 392.4348846466698, - "us_per_image_p50": 377.02504100001534, - "us_per_image_p90": 416.40049272801843, - "us_per_image_p99": 425.2599693668191, - "raw_times_s": [ - 21.31221778300096, - 18.701762863998738, - 18.851252050000767 - ], - "raw_throughput_ips": [ - 2346.072122061411, - 2673.544754235494, - 2652.3437205858145 - ] - } - ], - "num_images": 50000, - "num_images_total": 50000, - "num_images_decoded": 50000, - "num_images_skipped": 0, - "skip_rate": 0.0, - "skip_indices": [], - "skip_examples": [], - "num_runs": 3 -} \ No newline at end of file diff --git a/output/linux_Neoverse-V2/torchvision_default_results.json b/output/linux_Neoverse-V2/torchvision_default_results.json deleted file mode 100644 index 1d2345a..0000000 --- a/output/linux_Neoverse-V2/torchvision_default_results.json +++ /dev/null @@ -1,53 +0,0 @@ -{ - "library": "torchvision", - "mode": "memory", - "run_tag": "default", - "requested_threads": 0, - "effective_threads": 16, - "system_info": { - "Python": "3.12.3", - "OS": "Linux", - "OS Version": "#12~24.04.1-Ubuntu SMP Fri Mar 27 23:33:21 UTC 2026", - "Machine": "aarch64", - "CPU": { - "brand_raw": "Neoverse-V2", - "arch": "ARM_8", - "hz_advertised_raw": "Unknown", - "count": 16 - }, - "torchvision": "0.26.0+cpu" - }, - "benchmark_results": { - "images_per_second_mean": 642.7650456055452, - "images_per_second_std": 0.1587200078783516, - "images_per_second_p50": 642.7847444402202, - "images_per_second_p90": 642.9206641694485, - "images_per_second_p99": 642.9870040384299, - "us_per_image_mean": 1555.778534967998, - "us_per_image_p50": 1555.7307615800164, - "us_per_image_p90": 1556.1914824240084, - "us_per_image_p99": 1556.3967027424121, - "raw_times_s": [ - 77.78347007499906, - 77.78653807900082, - 77.76117790999888, - 77.82097525000063, - 77.79247242800011 - ], - "raw_throughput_ips": [ - 642.8100977211462, - 642.7847444402202, - 642.9943751349833, - 642.5003007142293, - 642.7357100171472 - ], - "num_images_total": 50000, - "num_images_decoded": 50000, - "num_images_skipped": 0, - "skip_rate": 0.0, - "skip_indices": [], - "skip_examples": [] - }, - "num_images": 50000, - "num_runs": 5 -} \ No newline at end of file diff --git a/output/linux_Neoverse-V2/turbojpeg_1t_results.json b/output/linux_Neoverse-V2/turbojpeg_1t_results.json deleted file mode 100644 index 52b4dca..0000000 --- a/output/linux_Neoverse-V2/turbojpeg_1t_results.json +++ /dev/null @@ -1,57 +0,0 @@ -{ - "library": "turbojpeg", - "mode": "memory", - "run_tag": "1t", - "requested_threads": 1, - "effective_threads": 1, - "system_info": { - "Python": "3.12.3", - "OS": "Linux", - "OS Version": "#12~24.04.1-Ubuntu SMP Fri Mar 27 23:33:21 UTC 2026", - "Machine": "aarch64", - "CPU": { - "brand_raw": "Neoverse-V2", - "arch": "ARM_8", - "hz_advertised_raw": "Unknown", - "count": 16 - }, - "turbojpeg": "1.8.3" - }, - "benchmark_results": { - "images_per_second_mean": 613.0980794491919, - "images_per_second_std": 0.20875770316851056, - "images_per_second_p50": 613.1678217524872, - "images_per_second_p90": 613.2948738145632, - "images_per_second_p99": 613.3540928002506, - "us_per_image_mean": 1631.0605912518165, - "us_per_image_p50": 1630.8748837176627, - "us_per_image_p90": 1631.7102393087857, - "us_per_image_p99": 1631.9874185507615, - "raw_times_s": [ - 81.56078244700075, - 81.54211331099941, - 81.59927879399947, - 81.51647509599934, - 81.5383428619989 - ], - "raw_throughput_ips": [ - 613.0274685936222, - 613.1678217524872, - 612.7382587072173, - 613.3606726875491, - 613.1961755050842 - ], - "num_images_total": 50000, - "num_images_decoded": 49999, - "num_images_skipped": 1, - "skip_rate": 2e-05, - "skip_indices": [ - 19876 - ], - "skip_examples": [ - "idx=19876: OSError: Unsupported color conversion request" - ] - }, - "num_images": 50000, - "num_runs": 5 -} \ No newline at end of file diff --git a/output/linux_Neoverse-V2/turbojpeg_dataloader_results.json b/output/linux_Neoverse-V2/turbojpeg_dataloader_results.json deleted file mode 100644 index d44fa4b..0000000 --- a/output/linux_Neoverse-V2/turbojpeg_dataloader_results.json +++ /dev/null @@ -1,125 +0,0 @@ -{ - "library": "turbojpeg", - "benchmark_type": "dataloader", - "batch_size": 32, - "num_threads": 1, - "system_info": { - "Python": "3.12.3", - "OS": "Linux", - "OS Version": "#12~24.04.1-Ubuntu SMP Fri Mar 27 23:33:21 UTC 2026", - "Machine": "aarch64", - "CPU": { - "brand_raw": "Neoverse-V2", - "arch": "ARM_8", - "hz_advertised_raw": "Unknown", - "count": 16 - }, - "turbojpeg": "1.8.3" - }, - "worker_results": [ - { - "num_workers": 0, - "num_warmup": 1, - "images_per_second_mean": 607.2414073824756, - "images_per_second_std": 0.1428851062671679, - "images_per_second_p50": 607.1651100001088, - "images_per_second_p90": 607.3863020701469, - "images_per_second_p99": 607.4360702859055, - "us_per_image_mean": 1646.7916107188619, - "us_per_image_p50": 1646.998458129158, - "us_per_image_p90": 1647.1017577151129, - "us_per_image_p99": 1647.1250001219528, - "raw_times_s": [ - 82.31079332199988, - 82.34827590799978, - 82.35473200299748 - ], - "raw_throughput_ips": [ - 607.4416000876565, - 607.1651100001088, - 607.1175120596613 - ] - }, - { - "num_workers": 2, - "num_warmup": 1, - "images_per_second_mean": 1093.8452331013193, - "images_per_second_std": 1.3572395068124037, - "images_per_second_p50": 1093.1022180483756, - "images_per_second_p90": 1095.2199767476532, - "images_per_second_p99": 1095.6964724549907, - "us_per_image_mean": 914.2075204637322, - "us_per_image_p50": 914.8275280105091, - "us_per_image_p90": 915.1076002360045, - "us_per_image_p99": 915.170616486741, - "raw_times_s": [ - 45.62995813700036, - 45.74046157299745, - 45.75796573700063 - ], - "raw_throughput_ips": [ - 1095.7494164224727, - 1093.1022180483756, - 1092.6840648331095 - ] - }, - { - "num_workers": 4, - "num_warmup": 1, - "images_per_second_mean": 2060.93437750792, - "images_per_second_std": 23.01604213373726, - "images_per_second_p50": 2059.171788124807, - "images_per_second_p90": 2083.8048313788076, - "images_per_second_p99": 2089.347266110958, - "us_per_image_mean": 485.2772576984874, - "us_per_image_p50": 485.63213898275774, - "us_per_image_p90": 490.5042558331327, - "us_per_image_p99": 491.600482124467, - "raw_times_s": [ - 24.281121316998906, - 24.585622530001274, - 23.923388975999842 - ], - "raw_throughput_ips": [ - 2059.171788124807, - 2033.6682522066449, - 2089.963092192308 - ] - }, - { - "num_workers": 8, - "num_warmup": 1, - "images_per_second_mean": 2388.7244759255377, - "images_per_second_std": 78.62119398711843, - "images_per_second_p50": 2374.191758562536, - "images_per_second_p90": 2468.002902435708, - "images_per_second_p99": 2489.110409807172, - "us_per_image_mean": 419.0836500263119, - "us_per_image_p50": 421.19596969937004, - "us_per_image_p90": 431.9857556710739, - "us_per_image_p99": 434.41345751470726, - "raw_times_s": [ - 21.73372542499783, - 21.059377288998803, - 20.068187539000064 - ], - "raw_throughput_ips": [ - 2300.5259808100755, - 2374.191758562536, - 2491.455688404001 - ] - } - ], - "num_images": 49999, - "num_images_total": 50000, - "num_images_decoded": 49999, - "num_images_skipped": 1, - "skip_rate": 2e-05, - "skip_indices": [ - 19876 - ], - "skip_examples": [ - "idx=19876: OSError: Unsupported color conversion request" - ], - "num_runs": 3 -} \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index 2b4b797..e757294 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -14,19 +14,20 @@ classifiers = [ "Programming Language :: Python :: 3.14", ] dependencies = [ + "jsonschema>=4.23", "numpy", "pandas", "py-cpuinfo", + "pyyaml>=6", "tqdm", "typer>=0.12", + "zstandard>=0.23", ] # Two groups, not thirteen. Real conflicts only exist between these two: # tensorflow vs torch — numpy/protobuf pin fights # Everything else (ajpegli, opencv, skimage, imageio, kornia-rs, simplejpeg, # turbojpeg, imagecodecs, pyvips, jpeg4py, torch, torchvision, Pillow) coexists in one venv. # -# Pillow-SIMD WAS a third group (incompatible with mainstream Pillow). Dropped -# 2026-04 — see docs/gcp_benchmarks.md "Why no Pillow-SIMD?" for rationale. optional-dependencies.mainstream = [ "ajpegli", "imagecodecs", @@ -35,7 +36,7 @@ optional-dependencies.mainstream = [ "kornia-rs", "opencv-python-headless", "pillow", - "pyturbojpeg<2", + "pyturbojpeg", "pyvips", # Bundled libvips wheels (Linux x86/arm + macOS x86/arm + Windows). Lets # pyvips run in CFFI API mode without `apt install libvips-dev` / @@ -68,9 +69,11 @@ entry-points."imread_benchmark.decoders".turbojpeg = "imread_benchmark.decoders. [dependency-groups] dev = [ + "mypy>=1.20", "pillow", "pre-commit>=3.5", "pytest>=8", + "types-pyyaml>=6.0.12", ] [tool.hatch] @@ -131,6 +134,7 @@ lint.ignore = [ "ANN401", "B027", "BLE001", + "CPY001", "D100", "D101", "D102", @@ -162,6 +166,7 @@ lint.per-file-ignores."imread_benchmark/cli.py" = [ "B008", # typer.Option(...) in defaults is the documented pattern "FBT001", # --skip-setup is a typer flag, must be a bool param "PLR0913", # `run` collects every CLI flag; splitting hurts UX, not complexity + "PLR0917", # Typer command parameters map directly to CLI options "S603", # subprocess into our own worker scripts with controlled argv "S607", # we require `uv` on PATH; absolute path would defeat the install docs ] @@ -171,7 +176,6 @@ lint.unfixable = [] lint.dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$" [tool.mypy] -python_version = "3.11" ignore_missing_imports = true follow_imports = "silent" warn_redundant_casts = true @@ -180,6 +184,7 @@ disallow_any_generics = true check_untyped_defs = true no_implicit_reexport = true disallow_untyped_defs = true +overrides = [ { module = [ "skimage.*", "tifffile.*" ], follow_imports = "skip" } ] [tool.pytest] ini_options.testpaths = [ "tests" ] diff --git a/scripts/install-libjpeg-turbo.sh b/scripts/install-libjpeg-turbo.sh new file mode 100755 index 0000000..4b9bae9 --- /dev/null +++ b/scripts/install-libjpeg-turbo.sh @@ -0,0 +1,35 @@ +#!/usr/bin/env bash +# Install the benchmark-pinned upstream libjpeg-turbo build on Debian/Ubuntu. + +set -euo pipefail + +if [[ $EUID -ne 0 ]]; then + echo "install-libjpeg-turbo.sh must run as root" >&2 + exit 2 +fi + +LIBJPEG_TURBO_VERSION=3.2.0 +case "$(dpkg --print-architecture)" in + amd64) + LIBJPEG_TURBO_PACKAGE="libjpeg-turbo-official_${LIBJPEG_TURBO_VERSION}_amd64.deb" + LIBJPEG_TURBO_SHA256=21297da4a4eb34ebefc54afca5d8dd86c0fdd6a9dfe49b1b962c5d1eeeafd8ec + ;; + arm64) + LIBJPEG_TURBO_PACKAGE="libjpeg-turbo-official_${LIBJPEG_TURBO_VERSION}_arm64.deb" + LIBJPEG_TURBO_SHA256=4bf51ee5dc130052ee19ac47cc2b7b613961dcd9519398fce976bead58be36eb + ;; + *) + echo "Unsupported architecture for official libjpeg-turbo: $(dpkg --print-architecture)" >&2 + exit 2 + ;; +esac + +LIBJPEG_TURBO_DEB="/tmp/$LIBJPEG_TURBO_PACKAGE" +curl -LsSf \ + "https://github.com/libjpeg-turbo/libjpeg-turbo/releases/download/$LIBJPEG_TURBO_VERSION/$LIBJPEG_TURBO_PACKAGE" \ + -o "$LIBJPEG_TURBO_DEB" +echo "$LIBJPEG_TURBO_SHA256 $LIBJPEG_TURBO_DEB" | sha256sum --check --strict +apt-get install -y -q "$LIBJPEG_TURBO_DEB" +printf '%s\n' /opt/libjpeg-turbo/lib64 > /etc/ld.so.conf.d/libjpeg-turbo-official.conf +ldconfig +/opt/libjpeg-turbo/bin/djpeg -version diff --git a/tests/factories.py b/tests/factories.py new file mode 100644 index 0000000..f9d3e39 --- /dev/null +++ b/tests/factories.py @@ -0,0 +1,130 @@ +from __future__ import annotations + +import os +from dataclasses import asdict + +from imread_benchmark.artifacts import BundleData, RunSample +from imread_benchmark.environments import EnvironmentDescriptor +from imread_benchmark.execution.spec import RunIdentity, compute_run_key +from imread_benchmark.plans import RunConfiguration +from imread_benchmark.platforms import PlatformDescriptor + + +def valid_bundle_data( # noqa: PLR0913 + *, + protocol_id: str = "decode-memory", + decoder_id: str = "pillow", + block_position: int = 0, + repetition: int = 0, + elapsed_seconds: tuple[float, float] = (2.0, 1.0), + platform_identity: dict[str, object] | None = None, + platform_provenance: dict[str, object] | None = None, +) -> tuple[str, BundleData]: + is_loader = protocol_id == "loader-supply" + configuration = RunConfiguration( + protocol_id=protocol_id, + decoder_id=decoder_id, + package_id="b" * 64, + manifest_id="c" * 64, + selection_id="d" * 64, + requested_threads=None, + num_workers=0 if is_loader else None, + batch_size=1 if is_loader else None, + prefetch_factor=None, + persistent_workers=False, + multiprocessing_start_method=None, + logical_repeat_factor=1, + warmup_passes=1, + timed_passes_per_run=2, + minimum_timed_seconds=1e-9, + output_contract="normalized-rgb", + support_policy="operational", + ) + environment = EnvironmentDescriptor.build( + dependency_group="mainstream", + lock_sha256="8" * 64, + project_sha256="9" * 64, + runner_revision="1" * 40, + python={"abi": "fixture-abi", "implementation": "cpython", "version": "3.12.0"}, + platform_tags=("fixture-platform",), + distributions=(("imread-benchmark", "0.2.0"),), + native_backends={}, + ) + platform = PlatformDescriptor.build( + identity=platform_identity or {"architecture": "fixture", "logical_cpu_count": 1, "machine_type": "fixture"}, + runtime={"kernel": "fixture"}, + provenance=platform_provenance, + ) + identity = RunIdentity( + plan_id="a" * 64, + platform_id=platform.platform_id, + environment_id=environment.environment_id, + runner_revision="1" * 40, + workload_id="fixture", + support_set_id="2" * 64, + support_item_ids=("item-1", "item-2"), + configuration=configuration, + repetition=repetition, + block_position=block_position, + ) + run_key = compute_run_key(identity) + runtime: dict[str, object] = { + "effective_threads": 1, + "process_id": os.getpid(), + "requested_threads": None, + } + if is_loader: + runtime.update( + { + "multiprocessing_start_method": "in-process", + "persistent_workers_reused": True, + "worker_handshakes": [ + { + "effective_threads": 1, + "generation": 0, + "multiprocessing_start_method": "in-process", + "process_id": os.getpid(), + }, + ], + }, + ) + return run_key, BundleData( + config={ + **asdict(configuration), + "block_position": identity.block_position, + "config_id": configuration.config_id, + "plan_id": identity.plan_id, + "repetition": identity.repetition, + "run_key": run_key, + "runner_revision": identity.runner_revision, + }, + dataset={ + "manifest_id": configuration.manifest_id, + "ordered_item_ids": list(identity.support_item_ids), + "package_id": configuration.package_id, + "selection_id": configuration.selection_id, + "support_audit_ids": ["7" * 64], + "support_policy": configuration.support_policy, + "support_process_context": "main-process", + "support_multiprocessing_start_method": configuration.multiprocessing_start_method, + "support_set_id": identity.support_set_id, + "workload_id": identity.workload_id, + }, + environment=environment.to_dict(), + platform=platform.to_dict(), + runtime=runtime, + samples=( + RunSample(sample_index=0, elapsed_seconds=elapsed_seconds[0], items_processed=2), + RunSample(sample_index=1, elapsed_seconds=elapsed_seconds[1], items_processed=2), + ), + events=( + {"duration_seconds": 0.1, "event": "validation"}, + {"duration_seconds": 0.1, "event": "warmup"}, + {"event": "measurement_complete", "sample_count": 2}, + ), + summary_fields={ + "logical_decodes_per_pass": 2, + "logical_repeat_factor": 1, + "num_unique_images": 2, + }, + ) diff --git a/tools/__init__.py b/tests/fixtures/__init__.py similarity index 100% rename from tools/__init__.py rename to tests/fixtures/__init__.py diff --git a/tests/fixtures/fake_gcloud.py b/tests/fixtures/fake_gcloud.py new file mode 100644 index 0000000..34e6013 --- /dev/null +++ b/tests/fixtures/fake_gcloud.py @@ -0,0 +1,57 @@ +from __future__ import annotations + +import json +import os +import shutil +import sys +from pathlib import Path + + +def _object_path(uri: str) -> Path: + if not uri.startswith("gs://"): + raise ValueError(f"not a GCS URI: {uri}") + return Path(os.environ["FAKE_GCS_ROOT"]) / uri.removeprefix("gs://") + + +def _copy(arguments: list[str]) -> int: + source, destination = arguments[2], arguments[3] + create_only = "--if-generation-match=0" in arguments + if source.startswith("gs://"): + source_path = _object_path(source) + if not source_path.is_file(): + print("404 Not Found", file=sys.stderr) + return 1 + destination_path = Path(destination) + else: + source_path = Path(source) + destination_path = _object_path(destination) + if create_only and destination_path.exists(): + print("412 Precondition Failed", file=sys.stderr) + return 1 + destination_path.parent.mkdir(parents=True, exist_ok=True) + shutil.copyfile(source_path, destination_path) + return 0 + + +def _describe(arguments: list[str]) -> int: + path = _object_path(arguments[3]) + if not path.is_file(): + print("404 Not Found", file=sys.stderr) + return 1 + stat = path.stat() + print(json.dumps({"generation": str(stat.st_mtime_ns), "size": str(stat.st_size)})) + return 0 + + +def main() -> int: + arguments = sys.argv[1:] + if arguments[:2] == ["storage", "cp"]: + return _copy(arguments) + if arguments[:3] == ["storage", "objects", "describe"]: + return _describe(arguments) + print(f"unsupported fake gcloud command: {arguments}", file=sys.stderr) + return 2 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/tests/fixtures/fake_run_worker.py b/tests/fixtures/fake_run_worker.py new file mode 100644 index 0000000..6d646c8 --- /dev/null +++ b/tests/fixtures/fake_run_worker.py @@ -0,0 +1,89 @@ +from __future__ import annotations + +import argparse +import json +import os +import signal +import subprocess +import sys +import time +from dataclasses import asdict +from pathlib import Path + +from imread_benchmark.artifacts import BundleData, RunSample, write_run_bundle +from imread_benchmark.environments import load_environment_descriptor +from imread_benchmark.execution.spec import load_run_spec +from imread_benchmark.platforms import load_platform_descriptor + + +def main() -> None: + parser = argparse.ArgumentParser() + parser.add_argument("--run-spec", required=True, type=Path) + parser.add_argument("--artifact-root", required=True, type=Path) + args = parser.parse_args() + spec = load_run_spec(args.run_spec) + configuration = spec.identity.configuration + environment = load_environment_descriptor(spec.environment_descriptor) + platform_descriptor = load_platform_descriptor(spec.platform_descriptor) + if configuration.decoder_id == "hard-crash": + os.kill(os.getpid(), signal.SIGSEGV) + if configuration.decoder_id == "slow": + child = subprocess.Popen( + (sys.executable, "-c", "import time; time.sleep(60)"), + ) + print(json.dumps({"child_process_id": child.pid}), flush=True) + time.sleep(60) + effective_threads = configuration.requested_threads if configuration.requested_threads is not None else 17 + item_count = len(spec.identity.support_item_ids) + write_run_bundle( + root=args.artifact_root / "runs", + run_key=spec.run_key, + data=BundleData( + config={ + **asdict(configuration), + "block_position": spec.identity.block_position, + "config_id": configuration.config_id, + "plan_id": spec.identity.plan_id, + "repetition": spec.identity.repetition, + "run_key": spec.run_key, + "runner_revision": spec.identity.runner_revision, + }, + dataset={ + "manifest_id": configuration.manifest_id, + "ordered_item_ids": list(spec.identity.support_item_ids), + "package_id": configuration.package_id, + "selection_id": configuration.selection_id, + "support_audit_ids": ["7" * 64], + "support_policy": configuration.support_policy, + "support_process_context": "main-process", + "support_multiprocessing_start_method": None, + "support_set_id": spec.identity.support_set_id, + "workload_id": spec.identity.workload_id, + }, + environment=environment.to_dict(), + platform=platform_descriptor.to_dict(), + runtime={ + "effective_threads": effective_threads, + "process_id": os.getpid(), + "requested_threads": configuration.requested_threads, + }, + samples=tuple( + RunSample(sample_index=index, elapsed_seconds=0.1, items_processed=item_count) + for index in range(configuration.timed_passes_per_run) + ), + summary_fields={ + "logical_decodes_per_pass": item_count * configuration.logical_repeat_factor, + "logical_repeat_factor": configuration.logical_repeat_factor, + "num_unique_images": item_count, + }, + events=( + {"duration_seconds": 0.01, "event": "validation"}, + {"duration_seconds": 0.01, "event": "warmup"}, + {"event": "measurement_complete", "sample_count": configuration.timed_passes_per_run}, + ), + ), + ) + + +if __name__ == "__main__": + main() diff --git a/tests/fixtures/fake_uv.py b/tests/fixtures/fake_uv.py new file mode 100644 index 0000000..7f781f2 --- /dev/null +++ b/tests/fixtures/fake_uv.py @@ -0,0 +1,22 @@ +from __future__ import annotations + +import json +import os +import sys +from pathlib import Path + + +def main() -> None: + log_path = Path(os.environ["FAKE_UV_LOG"]) + calls = json.loads(log_path.read_text()) if log_path.exists() else [] + calls.append({"argv": sys.argv[1:], "environment": os.environ["UV_PROJECT_ENVIRONMENT"]}) + log_path.write_text(json.dumps(calls)) + environment = Path(os.environ["UV_PROJECT_ENVIRONMENT"]) + bindir = environment / ("Scripts" if sys.platform == "win32" else "bin") + bindir.mkdir(parents=True) + target = bindir / ("python.exe" if sys.platform == "win32" else "python") + target.symlink_to(sys.executable) + + +if __name__ == "__main__": + main() diff --git a/tests/test_analysis_v2.py b/tests/test_analysis_v2.py new file mode 100644 index 0000000..e80d24d --- /dev/null +++ b/tests/test_analysis_v2.py @@ -0,0 +1,49 @@ +from __future__ import annotations + +from pathlib import Path + +import pytest + +from imread_benchmark.analysis import ClaimScope, assert_claim_scope, load_bundles +from imread_benchmark.artifacts import write_run_bundle +from tests.factories import valid_bundle_data + + +def _write_analysis_bundle(root: Path, *, protocol_id: str = "decode-memory") -> Path: + run_key, data = valid_bundle_data(protocol_id=protocol_id) + return write_run_bundle( + root=root / "runs", + run_key=run_key, + data=data, + ) + + +def test_canonical_loader_keeps_raw_samples_events_runtime_and_empty_failure_log(tmp_path: Path) -> None: + bundle = _write_analysis_bundle(tmp_path) + incomplete = tmp_path / "runs" / ("9" * 64) + incomplete.mkdir() + (incomplete / "summary.json").write_text("{}") + + (record,) = load_bundles(tmp_path) + + assert record.path == bundle + assert record.run_key == bundle.name + assert record.config["decoder_id"] == "pillow" + assert [sample.elapsed_seconds for sample in record.samples] == [2.0, 1.0] + assert record.failures == () + assert record.events[0]["event"] == "validation" + assert isinstance(record.runtime["process_id"], int) + assert record.summary["statistics"]["images_per_second"]["sample_std"] == pytest.approx( + 0.7071067811865476, + ) + + +def test_claim_gate_rejects_training_claims_from_loader_only_evidence(tmp_path: Path) -> None: + _write_analysis_bundle(tmp_path, protocol_id="loader-supply") + records = load_bundles(tmp_path) + + assert_claim_scope(records, ClaimScope.LOADER_SUPPLY) + with pytest.raises(ValueError, match="training"): + assert_claim_scope(records, ClaimScope.TRAINING) + with pytest.raises(ValueError, match="decode-memory"): + assert_claim_scope(records, ClaimScope.DECODER_CAPACITY) diff --git a/tests/test_benchmark.py b/tests/test_benchmark.py deleted file mode 100644 index cc8b3f9..0000000 --- a/tests/test_benchmark.py +++ /dev/null @@ -1,113 +0,0 @@ -from __future__ import annotations - -import numpy as np -import pytest - -from imread_benchmark.benchmark import run_timing_loop - - -def test_run_timing_loop_returns_expected_keys(): - items = [b"x"] * 10 - decode_fn = lambda data: np.zeros((4, 4, 3), dtype=np.uint8) # noqa: E731 - - result = run_timing_loop(decode_fn, items, num_runs=2, num_warmup=1) - - for key in ( - "images_per_second_mean", - "images_per_second_std", - "images_per_second_p50", - "images_per_second_p90", - "images_per_second_p99", - "us_per_image_mean", - "us_per_image_p50", - "raw_times_s", - "raw_throughput_ips", - "num_images_total", - "num_images_decoded", - "num_images_skipped", - "skip_rate", - "skip_indices", - "skip_examples", - ): - assert key in result, f"missing key: {key}" - assert len(result["raw_times_s"]) == 2 - assert len(result["raw_throughput_ips"]) == 2 - - -def test_run_timing_loop_throughput_positive(): - items = [b"x"] * 50 - decode_fn = lambda data: np.zeros((8, 8, 3), dtype=np.uint8) # noqa: E731 - - result = run_timing_loop(decode_fn, items, num_runs=3, num_warmup=0) - - assert result["images_per_second_mean"] > 0 - assert result["us_per_image_mean"] > 0 - assert all(t > 0 for t in result["raw_times_s"]) - assert all(ips > 0 for ips in result["raw_throughput_ips"]) - assert result["images_per_second_p50"] > 0 - assert result["images_per_second_p99"] > 0 - - -# ─── Skip-on-bad-image semantics ────────────────────────────────────────────── -# These exist to prove the partial-success path that vm_startup.sh now relies -# on. Without them, a future "tidy up the timing loop" PR could silently drop -# the discovery pass and revert us to the bug where one CMYK JPEG nukes a -# 4-hour cloud run. - - -def _flaky_decoder(bad_indices): - """Raise for listed item indices; succeed for all other indices.""" - bad = set(bad_indices) - counter = {"i": 0} - - def decode(_data): - idx = counter["i"] - counter["i"] += 1 - if idx in bad: - raise OSError(f"Unsupported color conversion request (idx={idx})") - return np.zeros((4, 4, 3), dtype=np.uint8) - - return decode - - -def test_skip_count_reflects_bad_items(): - items = [b"x"] * 20 - # Mark indices 3, 7, 11 as bad. _flaky_decoder uses a global counter so - # the same indices fail in the discovery pass. - decode_fn = _flaky_decoder([3, 7, 11]) - - result = run_timing_loop(decode_fn, items, num_runs=2, num_warmup=0) - - assert result["num_images_total"] == 20 - assert result["num_images_skipped"] == 3 - assert result["num_images_decoded"] == 17 - assert result["skip_indices"] == [3, 7, 11] - assert result["skip_rate"] == pytest.approx(3 / 20) - # Examples are capped + tagged with the exception type for triage. - assert len(result["skip_examples"]) == 3 - assert all("OSError" in ex for ex in result["skip_examples"]) - - -def test_skip_examples_are_capped(): - items = [b"x"] * 50 - # Every item bad — discovery should still cap retained examples to 3. - decode_fn = lambda _: (_ for _ in ()).throw( # noqa: E731 - ValueError("bad image"), - ) - - with pytest.raises(RuntimeError, match="failed on all"): - run_timing_loop(decode_fn, items, num_runs=1, num_warmup=0) - - -def test_zero_skips_keeps_clean_schema(): - items = [b"x"] * 10 - decode_fn = lambda _: np.zeros((4, 4, 3), dtype=np.uint8) # noqa: E731 - - result = run_timing_loop(decode_fn, items, num_runs=2, num_warmup=1) - - assert result["num_images_skipped"] == 0 - assert result["skip_rate"] == 0.0 - assert result["skip_indices"] == [] - assert result["skip_examples"] == [] - # Throughput stats should reflect ALL items (no spurious filtering). - assert result["num_images_decoded"] == 10 diff --git a/tests/test_bundle_v2.py b/tests/test_bundle_v2.py new file mode 100644 index 0000000..3b26d85 --- /dev/null +++ b/tests/test_bundle_v2.py @@ -0,0 +1,53 @@ +from __future__ import annotations + +import json +from pathlib import Path + +import pytest + +from imread_benchmark.artifacts import BundleValidationError, validate_run_bundle, write_run_bundle +from tests.factories import valid_bundle_data + + +def test_bundle_is_content_verified_and_summary_is_derived_from_samples(tmp_path: Path) -> None: + run_key, data = valid_bundle_data() + bundle = write_run_bundle( + root=tmp_path / "runs", + run_key=run_key, + data=data, + ) + + expected_files = { + "COMMITTED.json", + "bundle_manifest.json", + "config.json", + "dataset.json", + "environment.json", + "events.jsonl", + "failures.jsonl", + "platform.json", + "runtime.json", + "samples.jsonl", + "summary.json", + } + assert {path.name for path in bundle.iterdir()} == expected_files + summary = json.loads((bundle / "summary.json").read_text()) + commit = json.loads((bundle / "COMMITTED.json").read_text()) + assert summary["statistics"]["images_per_second"]["mean"] == 1.5 + assert summary["statistics"]["images_per_second"]["sample_std"] == pytest.approx(0.7071067811865476) + assert commit["run_key"] == run_key + assert len(commit["bundle_id"]) == 64 + assert (bundle / "failures.jsonl").read_text() == "" + validate_run_bundle(bundle, expected_run_key=run_key) + + samples_path = bundle / "samples.jsonl" + samples_path.write_text(samples_path.read_text().replace('"elapsed_seconds": 2.0', '"elapsed_seconds": 3.0')) + with pytest.raises(BundleValidationError, match="checksum mismatch"): + validate_run_bundle(bundle, expected_run_key=run_key) + + +def test_bundle_recomputes_run_identity_across_config_dataset_environment_and_platform(tmp_path: Path) -> None: + _, data = valid_bundle_data() + + with pytest.raises(BundleValidationError, match=r"run_key.*identity"): + write_run_bundle(root=tmp_path / "runs", run_key="9" * 64, data=data) diff --git a/tests/test_campaign_v2.py b/tests/test_campaign_v2.py new file mode 100644 index 0000000..4749194 --- /dev/null +++ b/tests/test_campaign_v2.py @@ -0,0 +1,131 @@ +from __future__ import annotations + +import json +import sys +from pathlib import Path + +from imread_benchmark.datasets.materializer import LocalObjectStore +from imread_benchmark.datasets.package import build_dataset_package +from imread_benchmark.environments import EnvironmentDescriptor, write_environment_descriptor +from imread_benchmark.execution.campaign import CampaignConfig, run_campaign +from imread_benchmark.execution.coordinator import AttemptStatus, RemoteCheckpoint +from imread_benchmark.platforms import PlatformDescriptor, write_platform_descriptor + + +def test_campaign_runs_support_then_isolated_matrix_and_resumes_on_fresh_machine( + tmp_path: Path, + jpeg_dir: Path, +) -> None: + descriptor_path = build_dataset_package( + package_name="campaign-fixture", + workloads={"mixed": jpeg_dir}, + output_root=tmp_path / "packages", + provenance={"source": "pytest"}, + ) + descriptor = json.loads(descriptor_path.read_text()) + workload = descriptor["workloads"]["mixed"] + plan_path = tmp_path / "experiment.yaml" + plan_path.write_text(_plan_yaml(descriptor, workload)) + environment = EnvironmentDescriptor.build( + dependency_group="mainstream", + lock_sha256="1" * 64, + project_sha256="2" * 64, + runner_revision="3" * 40, + python={"abi": "fixture", "implementation": "cpython", "version": "3.12.0"}, + platform_tags=("fixture",), + distributions=(("imread-benchmark", "0.2.0"), ("pillow", "fixture"), ("torch", "fixture")), + native_backends={}, + ) + platform = PlatformDescriptor.build( + identity={"architecture": "fixture", "logical_cpu_count": 4, "machine_type": "fixture"}, + runtime={"available_multiprocessing_start_methods": ["spawn"], "memory_bytes": 1024**3}, + ) + environment_path = write_environment_descriptor(tmp_path / "environment.json", environment) + platform_path = write_platform_descriptor(tmp_path / "platform.json", platform) + remote = RemoteCheckpoint(LocalObjectStore(tmp_path / "object-store")) + + first = run_campaign( + CampaignConfig( + plan_path=plan_path, + package_descriptor=descriptor_path, + environment_descriptor=environment_path, + platform_descriptor=platform_path, + artifact_root=tmp_path / "machine-one" / "artifacts", + attempts_root=tmp_path / "machine-one" / "attempts", + runner_revision="3" * 40, + worker_python=Path(sys.executable), + remote=remote, + ), + ) + second = run_campaign( + CampaignConfig( + plan_path=plan_path, + package_descriptor=descriptor_path, + environment_descriptor=environment_path, + platform_descriptor=platform_path, + artifact_root=tmp_path / "machine-two" / "artifacts", + attempts_root=tmp_path / "machine-two" / "attempts", + runner_revision="3" * 40, + worker_python=Path(sys.executable), + remote=remote, + ), + ) + + assert first.complete is True + assert [result.status for result in first.run_results] == [ + AttemptStatus.COMPLETED, + AttemptStatus.COMPLETED, + AttemptStatus.COMPLETED, + ] + assert [result.status for result in second.run_results] == [ + AttemptStatus.SKIPPED, + AttemptStatus.SKIPPED, + AttemptStatus.SKIPPED, + ] + assert first.support_audit_count == 2 + assert first.support_set_ids == second.support_set_ids + assert len(first.support_set_ids) == 2 + + +def _plan_yaml(descriptor: dict[str, object], workload: dict[str, object]) -> str: + return f"""\ +schema_version: "2.0" +experiment_name: campaign-fixture +seed: 7 +repetitions: 1 +dataset: + descriptor: unavailable/package.json + package_id: "{descriptor["package_id"]}" + workload_id: mixed + manifest_id: "{workload["manifest_id"]}" + selection: + method: all + expected_items: 4 + logical_repeat_factor: 1 +matrix: + decoders: + pillow: + threads: [default] + protocols: + decode-memory: {{}} + loader-supply: + worker_profiles: + - workers: [0] + batch_size: 1 + - workers: [2] + batch_size: 1 + multiprocessing_start_method: spawn + prefetch_factor: 1 + persistent_workers: true +measurement: + warmup_passes: 1 + timed_passes_per_run: 2 + minimum_timed_seconds: 0.000000001 + output_contract: normalized-rgb + support_policy: common +execution: + per_run_subprocess: true + run_timeout_seconds: 30 + checkpoint_each_run: true + maximum_memory_fraction: 0.6 +""" diff --git a/tests/test_cli_v2.py b/tests/test_cli_v2.py new file mode 100644 index 0000000..49eb89b --- /dev/null +++ b/tests/test_cli_v2.py @@ -0,0 +1,125 @@ +from __future__ import annotations + +import json +from pathlib import Path + +from PIL import Image +from typer.testing import CliRunner + +from imread_benchmark.cli import app +from imread_benchmark.datasets.package import build_dataset_package, open_dataset_package + +runner = CliRunner() + + +def test_cli_exposes_only_schema_two_workflows() -> None: + result = runner.invoke(app, ["--help"]) + + assert result.exit_code == 0 + assert "campaign" in result.stdout + assert "dataset" in result.stdout + assert "environment" in result.stdout + assert "render-readme" not in result.stdout + + +def test_dataset_package_command_builds_a_valid_content_addressed_package( + tmp_path: Path, + jpeg_dir: Path, +) -> None: + result = runner.invoke( + app, + [ + "dataset", + "package", + "--name", + "cli-fixture", + "--workload", + f"mixed={jpeg_dir}", + "--output-root", + str(tmp_path / "packages"), + "--provenance-source", + "pytest", + ], + ) + + assert result.exit_code == 0, result.stdout + document = json.loads(result.stdout) + package = open_dataset_package(document["descriptor"]) + assert package.descriptor["schema_version"] == "2.0" + assert len(package.read_workload_items("mixed")) == 4 + + +def test_controlled_package_command_builds_the_requested_factor_cell(tmp_path: Path) -> None: + sources = tmp_path / "sources" + sources.mkdir() + Image.new("RGB", (64, 48), (20, 40, 60)).save(sources / "scene.png", format="PNG") + + result = runner.invoke( + app, + [ + "dataset", + "controlled-package", + "--source-dir", + str(sources), + "--output-root", + str(tmp_path / "controlled"), + "--source-name", + "fixture-lossless", + "--source-release", + "test-release", + "--source-license", + "fixture-only", + "--long-edge", + "32", + "--quality", + "75", + "--no-native", + "--compressed-byte-limit", + str(1024**2), + ], + ) + + assert result.exit_code == 0, result.stdout + document = json.loads(result.stdout) + package = open_dataset_package(document["descriptor"]) + assert set(package.descriptor["workloads"]) == {"controlled-le0032-q075"} + + +def test_list_decoders_is_machine_readable() -> None: + result = runner.invoke(app, ["list-decoders"]) + + assert result.exit_code == 0 + rows = json.loads(result.stdout) + assert {row["decoder_id"] for row in rows} >= {"pillow", "opencv"} + assert all(row["schema_version"] == "2.0" for row in rows) + + +def test_plan_instantiate_command_generates_valid_workload_plan(tmp_path: Path, jpeg_dir: Path) -> None: + descriptor_path = build_dataset_package( + package_name="fixture-jpegs", + workloads={"fixture": jpeg_dir}, + output_root=tmp_path / "packages", + provenance={"source": "pytest"}, + ) + template = Path(__file__).parents[1] / "examples" / "fodb-experiment.template.yaml" + + result = runner.invoke( + app, + [ + "plan", + "instantiate", + str(template), + "--package-descriptor", + str(descriptor_path), + "--output-dir", + str(tmp_path / "plans"), + "--workload", + "fixture", + ], + ) + + assert result.exit_code == 0, result.stdout + document = json.loads(result.stdout) + assert document["schema_version"] == "2.0" + assert document["plans"][0]["workload_id"] == "fixture" + assert Path(document["plans"][0]["path"]).is_file() diff --git a/tests/test_controlled_dataset.py b/tests/test_controlled_dataset.py new file mode 100644 index 0000000..0b3b6db --- /dev/null +++ b/tests/test_controlled_dataset.py @@ -0,0 +1,188 @@ +from __future__ import annotations + +import json +from pathlib import Path + +import numpy as np +import pytest +from PIL import Image + +from imread_benchmark.datasets.controlled import ControlledDatasetError, prepare_controlled_ablation +from imread_benchmark.datasets.package import open_dataset_package + + +def _write_png(path: Path, *, width: int, height: int, seed: int) -> None: + generator = np.random.default_rng(seed) + pixels = generator.integers(0, 256, (height, width, 3), dtype=np.uint8) + Image.fromarray(pixels, mode="RGB").save(path, format="PNG") + + +def test_controlled_ablation_builds_matched_resolution_quality_workloads(tmp_path: Path) -> None: + sources = tmp_path / "sources" + sources.mkdir() + _write_png(sources / "scene-b.png", width=80, height=60, seed=2) + _write_png(sources / "scene-a.png", width=96, height=64, seed=1) + + descriptor_path = prepare_controlled_ablation( + sources, + tmp_path / "controlled", + source_name="fixture-lossless", + source_release="2026-07-29", + source_license="fixture-only", + source_url="https://example.test/fixture", + long_edges=(32, 48), + qualities=(50, 90), + include_native=True, + subsampling="4:2:0", + seed=17, + compressed_byte_limit=1024**2, + ) + package = open_dataset_package(descriptor_path) + provenance = package.descriptor["provenance"] + expected_workloads = { + "controlled-le0032-q050", + "controlled-le0032-q090", + "controlled-le0048-q050", + "controlled-le0048-q090", + "controlled-native-q050", + "controlled-native-q090", + } + + assert set(package.descriptor["workloads"]) == expected_workloads + assert provenance["design"]["factors"] == { + "encoder_quality": [50, 90], + "long_edge_pixels": [32, 48, "native"], + } + assert provenance["design"]["controls"]["chroma_subsampling"] == "4:2:0" + assert provenance["design"]["controls"]["source_metadata"] == "stripped" + assert provenance["encoder"]["implementation"] == "Pillow" + assert provenance["source_dataset"] == { + "license": "fixture-only", + "name": "fixture-lossless", + "release": "2026-07-29", + "url": "https://example.test/fixture", + } + assert len(provenance["sources"]) == 2 + assert str(sources) not in json.dumps(provenance) + + ordered_source_ids = provenance["ordered_source_ids"] + assert len(ordered_source_ids) == 2 + for workload_id in expected_workloads: + workload = provenance["workloads"][workload_id] + assert workload["ordered_source_ids"] == ordered_source_ids + assert workload["item_count"] == 2 + items = package.read_workload_items(workload_id) + assert len(items) == 2 + assert all(item.compressed_bytes > 0 for item in items) + manifest_path = package.root / package.descriptor["workloads"][workload_id]["manifest"] + manifest = json.loads(manifest_path.read_text()) + assert all(item["subsampling"] == "4:2:0" for item in manifest["items"]) + assert all(item["has_exif"] is False for item in manifest["items"]) + + le32_items = package.read_workload_items("controlled-le0032-q050") + assert all(max(_jpeg_size(item.data)) == 32 for item in le32_items) + native_sizes = {tuple(source["normalized_rgb_size"]) for source in provenance["sources"]} + assert {_jpeg_size(item.data) for item in package.read_workload_items("controlled-native-q050")} == native_sizes + assert _quantization_tables(le32_items[0].data) != _quantization_tables( + package.read_workload_items("controlled-le0032-q090")[0].data, + ) + + +def test_controlled_ablation_is_content_deterministic(tmp_path: Path) -> None: + sources = tmp_path / "sources" + sources.mkdir() + _write_png(sources / "scene.png", width=64, height=48, seed=3) + + first = prepare_controlled_ablation( + sources, + tmp_path / "first", + source_name="fixture-lossless", + source_release="test-release", + source_license="fixture-only", + long_edges=(32,), + qualities=(75,), + include_native=False, + compressed_byte_limit=1024**2, + ) + second = prepare_controlled_ablation( + sources, + tmp_path / "second", + source_name="fixture-lossless", + source_release="test-release", + source_license="fixture-only", + long_edges=(32,), + qualities=(75,), + include_native=False, + compressed_byte_limit=1024**2, + ) + + assert json.loads(first.read_text()) == json.loads(second.read_text()) + + +def test_controlled_ablation_rejects_non_lossless_or_undersized_sources( + tmp_path: Path, + jpeg_bytes: bytes, +) -> None: + sources = tmp_path / "sources" + sources.mkdir() + (sources / "already-lossy.jpg").write_bytes(jpeg_bytes) + + with pytest.raises(ControlledDatasetError, match="lossless PNG"): + prepare_controlled_ablation( + sources, + tmp_path / "controlled", + source_name="fixture-lossless", + source_release="test-release", + source_license="fixture-only", + long_edges=(32,), + qualities=(75,), + ) + + (sources / "already-lossy.jpg").unlink() + _write_png(sources / "small.png", width=24, height=16, seed=4) + with pytest.raises(ControlledDatasetError, match="smaller than requested long edge"): + prepare_controlled_ablation( + sources, + tmp_path / "controlled", + source_name="fixture-lossless", + source_release="test-release", + source_license="fixture-only", + long_edges=(32,), + qualities=(75,), + ) + + +def test_controlled_ablation_enforces_each_resident_workload_byte_budget(tmp_path: Path) -> None: + sources = tmp_path / "sources" + sources.mkdir() + _write_png(sources / "scene.png", width=64, height=48, seed=5) + + with pytest.raises(ControlledDatasetError, match="resident compressed-byte budget"): + prepare_controlled_ablation( + sources, + tmp_path / "controlled", + source_name="fixture-lossless", + source_release="test-release", + source_license="fixture-only", + long_edges=(32,), + qualities=(75,), + compressed_byte_limit=1, + ) + + +def _jpeg_size(data: bytes) -> tuple[int, int]: + from io import BytesIO + + with Image.open(BytesIO(data)) as image: + image.load() + assert image.format == "JPEG" + assert not image.getexif() + return image.size + + +def _quantization_tables(data: bytes) -> dict[int, list[int]]: + from io import BytesIO + + with Image.open(BytesIO(data)) as image: + image.load() + return image.quantization diff --git a/tests/test_dataset_package.py b/tests/test_dataset_package.py new file mode 100644 index 0000000..dddcbe7 --- /dev/null +++ b/tests/test_dataset_package.py @@ -0,0 +1,158 @@ +from __future__ import annotations + +import hashlib +import json +import os +import tarfile +from pathlib import Path + +import pytest + +from imread_benchmark.datasets.package import DatasetPackageError, build_dataset_package, open_dataset_package + + +def test_package_reads_manifest_order_directly_from_content_addressed_tar( + tmp_path: Path, + jpeg_dir: Path, +) -> None: + descriptor_path = build_dataset_package( + package_name="fixture-jpegs", + workloads={"fixture": jpeg_dir}, + output_root=tmp_path / "packages", + provenance={"source": "pytest"}, + ) + + descriptor = json.loads(descriptor_path.read_text()) + package_root = descriptor_path.parent + archive_path = package_root / descriptor["archive"]["file"] + reader = open_dataset_package(descriptor_path) + + assert descriptor["schema_version"] == "2.0" + assert descriptor_path == package_root / "package.json" + assert package_root.name == descriptor["package_id"] + assert archive_path.suffix == ".tar" + assert archive_path.is_file() + assert not list(package_root.rglob("*.jpg")) + workload = descriptor["workloads"]["fixture"] + manifest = json.loads((package_root / workload["manifest"]).read_text()) + assert manifest["schema_version"] == "2.0" + assert manifest["manifest_id"] == workload["manifest_id"] + resident_items = reader.read_workload_items("fixture") + assert tuple(item.item_id for item in resident_items) == tuple(item["item_id"] for item in manifest["items"]) + assert reader.read_workload("fixture") == tuple(path.read_bytes() for path in sorted(jpeg_dir.glob("*.jpg"))) + + +def test_open_package_rejects_manifest_that_no_longer_matches_descriptor( + tmp_path: Path, + jpeg_dir: Path, +) -> None: + descriptor_path = build_dataset_package( + package_name="fixture-jpegs", + workloads={"fixture": jpeg_dir}, + output_root=tmp_path / "packages", + provenance={"source": "pytest"}, + ) + descriptor = json.loads(descriptor_path.read_text()) + manifest_path = descriptor_path.parent / descriptor["workloads"]["fixture"]["manifest"] + manifest = json.loads(manifest_path.read_text()) + manifest["items"][0]["relative_path"] = "tampered.jpg" + manifest_path.write_text(json.dumps(manifest)) + + with pytest.raises(DatasetPackageError, match="manifest identity mismatch"): + open_dataset_package(descriptor_path) + + +def test_open_package_audits_archive_index_against_tar_members( + tmp_path: Path, + jpeg_dir: Path, +) -> None: + descriptor_path = build_dataset_package( + package_name="fixture-jpegs", + workloads={"fixture": jpeg_dir}, + output_root=tmp_path / "packages", + provenance={"source": "pytest"}, + ) + package_root = descriptor_path.parent + descriptor = json.loads(descriptor_path.read_text()) + index_path = package_root / descriptor["archive_index"]["file"] + archive_index = json.loads(index_path.read_text()) + first_entry = next(iter(archive_index["blobs"].values())) + first_entry["offset"] += 1 + index_path.write_text(json.dumps(archive_index, sort_keys=True)) + descriptor["archive_index"]["sha256"] = hashlib.sha256(index_path.read_bytes()).hexdigest() + moved_root = _rewrite_package_identity(descriptor_path, descriptor) + + with pytest.raises(DatasetPackageError, match="archive index does not match tar members"): + open_dataset_package(moved_root / "package.json") + + +def test_open_package_rejects_unsafe_tar_members(tmp_path: Path, jpeg_dir: Path) -> None: + descriptor_path = build_dataset_package( + package_name="fixture-jpegs", + workloads={"fixture": jpeg_dir}, + output_root=tmp_path / "packages", + provenance={"source": "pytest"}, + ) + package_root = descriptor_path.parent + descriptor = json.loads(descriptor_path.read_text()) + archive_path = package_root / descriptor["archive"]["file"] + with tarfile.open(archive_path, mode="a") as archive: + unsafe = tarfile.TarInfo("../escape") + unsafe.type = tarfile.SYMTYPE + unsafe.linkname = "../../outside" + archive.addfile(unsafe) + archive_sha256 = hashlib.sha256(archive_path.read_bytes()).hexdigest() + renamed_archive = archive_path.with_name(f"{archive_sha256}.tar") + archive_path.rename(renamed_archive) + descriptor["archive"] = { + **descriptor["archive"], + "bytes": renamed_archive.stat().st_size, + "file": renamed_archive.name, + "sha256": archive_sha256, + } + moved_root = _rewrite_package_identity(descriptor_path, descriptor) + + with pytest.raises(DatasetPackageError, match="unsafe tar member"): + open_dataset_package(moved_root / "package.json") + + +def test_package_stores_shared_workload_content_once(tmp_path: Path, jpeg_bytes: bytes) -> None: + corpus = tmp_path / "corpus" + native = tmp_path / "native" + mixed = tmp_path / "mixed" + corpus.mkdir() + native.mkdir() + mixed.mkdir() + source = corpus / "source.jpg" + source.write_bytes(jpeg_bytes) + os.link(source, native / "native.jpg") + os.link(source, mixed / "first.jpg") + os.link(source, mixed / "second.jpg") + + descriptor_path = build_dataset_package( + package_name="hardlinked-views", + workloads={"native": native, "mixed": mixed}, + output_root=tmp_path / "packages", + provenance={"source": "pytest"}, + ) + descriptor = json.loads(descriptor_path.read_text()) + archive_path = descriptor_path.parent / descriptor["archive"]["file"] + with tarfile.open(archive_path, mode="r:") as archive: + blob_members = [member for member in archive if member.name.startswith("blobs/")] + mixed_bytes = open_dataset_package(descriptor_path).read_workload("mixed") + + assert len(blob_members) == 1 + assert len(mixed_bytes) == 2 + assert mixed_bytes[0] is mixed_bytes[1] + + +def _rewrite_package_identity(descriptor_path: Path, descriptor: dict[str, object]) -> Path: + package_root = descriptor_path.parent + descriptor_without_id = {key: value for key, value in descriptor.items() if key != "package_id"} + descriptor["package_id"] = hashlib.sha256( + json.dumps(descriptor_without_id, ensure_ascii=False, separators=(",", ":"), sort_keys=True).encode(), + ).hexdigest() + descriptor_path.write_text(json.dumps(descriptor, sort_keys=True)) + moved_root = package_root.parent / str(descriptor["package_id"]) + package_root.rename(moved_root) + return moved_root diff --git a/tests/test_decoders.py b/tests/test_decoders.py index 3aae90f..e2cef76 100644 --- a/tests/test_decoders.py +++ b/tests/test_decoders.py @@ -12,7 +12,9 @@ import numpy as np import pytest +from imread_benchmark.contracts import OutputContract, validate_output from imread_benchmark.decoders import REGISTRY, BaseDecoder +from imread_benchmark.decoders.pillow_decoder import PillowDecoder from .conftest import is_decoder_available @@ -23,6 +25,7 @@ def test_decode_from_bytes_shape_and_dtype(decoder_cls, jpeg_bytes): decoder = decoder_cls() result = decoder.decode(jpeg_bytes) + validate_output(result, OutputContract.normalized_rgb()) assert isinstance(result, np.ndarray), f"{decoder.name}: decode() must return ndarray" assert result.ndim == 3, f"{decoder.name}: expected 3-D array, got {result.ndim}-D" assert result.shape[2] == 3, f"{decoder.name}: expected 3 channels, got {result.shape[2]}" @@ -33,6 +36,7 @@ def test_decode_from_bytes_shape_and_dtype(decoder_cls, jpeg_bytes): def test_decode_path(decoder_cls, jpeg_path): decoder = decoder_cls() result = decoder.decode_path(str(jpeg_path)) + validate_output(result, OutputContract.normalized_rgb()) assert isinstance(result, np.ndarray) assert result.ndim == 3 assert result.shape[2] == 3 @@ -47,6 +51,14 @@ def test_decode_nonzero_output(decoder_cls, jpeg_bytes): assert result.max() > 0, f"{decoder.name}: decoded image is all zeros" +def test_pillow_returns_materialized_owned_rgb(jpeg_bytes): + result = PillowDecoder().decode(jpeg_bytes) + + assert result.flags.owndata + assert result.flags.writeable + assert result.shape == (48, 64, 3) + + # ─── Decoder metadata / contract ────────────────────────────────────────────── # These tests don't require the third-party library — they just inspect the # class. So we parametrize over the FULL registry, not `available_decoders`. diff --git a/tests/test_environment_provision.py b/tests/test_environment_provision.py new file mode 100644 index 0000000..140ac24 --- /dev/null +++ b/tests/test_environment_provision.py @@ -0,0 +1,108 @@ +from __future__ import annotations + +import json +import sys +from pathlib import Path + +from imread_benchmark.environments import EnvironmentDescriptor, load_environment_descriptor +from imread_benchmark.environments.provision import EnvironmentRequest, provision_environment + + +def test_frozen_environment_is_built_atomically_and_reused(tmp_path: Path, monkeypatch) -> None: + project = tmp_path / "project" + project.mkdir() + (project / "uv.lock").write_text("fixture lock\n") + (project / "pyproject.toml").write_text("[project]\nname='fixture'\n") + log_path = tmp_path / "uv-calls.json" + monkeypatch.setenv("FAKE_UV_LOG", str(log_path)) + request = EnvironmentRequest( + project_root=project, + cache_root=tmp_path / "cache", + dependency_group="mainstream", + runner_revision="1" * 40, + python_executable=Path(sys.executable), + native_backends=(("fixture", "1.0"),), + uv_command=(sys.executable, str(Path(__file__).parent / "fixtures" / "fake_uv.py")), + ) + + def probe(_python: Path, key: str) -> EnvironmentDescriptor: + return EnvironmentDescriptor.build( + dependency_group=request.dependency_group, + lock_sha256=request.lock_sha256, + project_sha256=request.project_sha256, + runner_revision=request.runner_revision, + python=request.python_identity, + platform_tags=request.platform_tags, + distributions=(("fixture", "1.0"),), + native_backends=dict(request.native_backends), + ) + + first = provision_environment(request, probe=probe) + second = provision_environment(request, probe=probe) + + assert first == second + assert first.cache_hit is False + assert second.cache_hit is True + assert first.python_executable.is_file() + assert load_environment_descriptor(first.descriptor_path).environment_id == first.environment_id + marker = json.loads((first.root / ".READY.json").read_text()) + assert marker["environment_key"] == request.environment_key + assert marker["environment_id"] == first.environment_id + calls = json.loads(log_path.read_text()) + assert len(calls) == 1 + assert "--frozen" in calls[0]["argv"] + assert "--no-editable" in calls[0]["argv"] + assert calls[0]["argv"][-2:] == ["--extra", "mainstream"] + + +def test_environment_key_changes_with_lock_python_group_revision_or_native_backend(tmp_path: Path) -> None: + project = tmp_path / "project" + project.mkdir() + (project / "uv.lock").write_text("first\n") + (project / "pyproject.toml").write_text("[project]\nname='fixture'\n") + base = EnvironmentRequest( + project_root=project, + cache_root=tmp_path / "cache", + dependency_group="mainstream", + runner_revision="1" * 40, + python_executable=Path(sys.executable), + ) + (project / "uv.lock").write_text("second\n") + lock_changed = EnvironmentRequest( + project_root=project, + cache_root=tmp_path / "cache", + dependency_group="mainstream", + runner_revision="1" * 40, + python_executable=Path(sys.executable), + ) + group_changed = EnvironmentRequest( + project_root=project, + cache_root=tmp_path / "cache", + dependency_group="tensorflow", + runner_revision="1" * 40, + python_executable=Path(sys.executable), + ) + revision_changed = EnvironmentRequest( + project_root=project, + cache_root=tmp_path / "cache", + dependency_group="mainstream", + runner_revision="2" * 40, + python_executable=Path(sys.executable), + ) + backend_changed = EnvironmentRequest( + project_root=project, + cache_root=tmp_path / "cache", + dependency_group="mainstream", + runner_revision="1" * 40, + python_executable=Path(sys.executable), + native_backends=(("libjpeg-turbo", "3.2.0"),), + ) + + keys = { + base.environment_key, + lock_changed.environment_key, + group_changed.environment_key, + revision_changed.environment_key, + backend_changed.environment_key, + } + assert len(keys) == 5 diff --git a/tests/test_environment_remote_cache.py b/tests/test_environment_remote_cache.py new file mode 100644 index 0000000..2e7fae3 --- /dev/null +++ b/tests/test_environment_remote_cache.py @@ -0,0 +1,127 @@ +from __future__ import annotations + +import json +import sys +import tempfile +from dataclasses import dataclass +from pathlib import Path + +from imread_benchmark.datasets.materializer import LocalObjectStore, ObjectMetadata +from imread_benchmark.environments import EnvironmentDescriptor +from imread_benchmark.environments.cache import materialize_environment_cache, publish_environment_cache +from imread_benchmark.environments.provision import EnvironmentRequest, provision_environment + + +@dataclass +class _RacingMarkerStore: + base: LocalObjectStore + raced: bool = False + + def put_create_only(self, source: Path, key: str) -> None: + if "/keys/" in key and not self.raced: + self.raced = True + document = json.loads(source.read_text()) + document["race_winner"] = True + with tempfile.TemporaryDirectory() as directory: + winner = Path(directory) / "marker.json" + winner.write_text(json.dumps(document, sort_keys=True)) + self.base.put_create_only(winner, key) + self.base.put_create_only(source, key) + + def download(self, key: str, destination: Path) -> None: + self.base.download(key, destination) + + def metadata(self, key: str) -> ObjectMetadata: + return self.base.metadata(key) + + +def test_remote_environment_cache_is_content_addressed_verified_and_reusable( + tmp_path: Path, + monkeypatch, +) -> None: + project = tmp_path / "project" + project.mkdir() + (project / "uv.lock").write_text("fixture lock\n") + (project / "pyproject.toml").write_text("[project]\nname='fixture'\n") + monkeypatch.setenv("FAKE_UV_LOG", str(tmp_path / "uv-calls.json")) + fake_uv = (sys.executable, str(Path(__file__).parent / "fixtures" / "fake_uv.py")) + first_request = EnvironmentRequest( + project_root=project, + cache_root=tmp_path / "cache-one", + dependency_group="mainstream", + runner_revision="1" * 40, + python_executable=Path(sys.executable), + uv_command=fake_uv, + ) + + def probe(_python: Path, _key: str) -> EnvironmentDescriptor: + return EnvironmentDescriptor.build( + dependency_group=first_request.dependency_group, + lock_sha256=first_request.lock_sha256, + project_sha256=first_request.project_sha256, + runner_revision=first_request.runner_revision, + python=first_request.python_identity, + platform_tags=first_request.platform_tags, + distributions=(("fixture", "1.0"),), + native_backends={}, + ) + + built = provision_environment(first_request, probe=probe) + store = LocalObjectStore(tmp_path / "object-store") + marker_key = publish_environment_cache(built.root, store=store, prefix="environments") + assert publish_environment_cache(built.root, store=store, prefix="environments") == marker_key + + second_request = EnvironmentRequest( + project_root=project, + cache_root=tmp_path / "cache-two", + dependency_group="mainstream", + runner_revision="1" * 40, + python_executable=Path(sys.executable), + uv_command=fake_uv, + ) + restored = materialize_environment_cache(second_request, store=store, prefix="environments") + + assert restored is not None + assert restored.environment_id == built.environment_id + assert restored.environment_key == built.environment_key + assert restored.python_executable.is_file() + assert restored.cache_hit is True + + +def test_concurrent_environment_marker_accepts_the_first_matching_winner( + tmp_path: Path, + monkeypatch, +) -> None: + project = tmp_path / "project" + project.mkdir() + (project / "uv.lock").write_text("fixture lock\n") + (project / "pyproject.toml").write_text("[project]\nname='fixture'\n") + monkeypatch.setenv("FAKE_UV_LOG", str(tmp_path / "uv-calls.json")) + request = EnvironmentRequest( + project_root=project, + cache_root=tmp_path / "cache", + dependency_group="mainstream", + runner_revision="1" * 40, + python_executable=Path(sys.executable), + uv_command=(sys.executable, str(Path(__file__).parent / "fixtures" / "fake_uv.py")), + ) + + def probe(_python: Path, _key: str) -> EnvironmentDescriptor: + return EnvironmentDescriptor.build( + dependency_group=request.dependency_group, + lock_sha256=request.lock_sha256, + project_sha256=request.project_sha256, + runner_revision=request.runner_revision, + python=request.python_identity, + platform_tags=request.platform_tags, + distributions=(("fixture", "1.0"),), + native_backends={}, + ) + + built = provision_environment(request, probe=probe) + store = _RacingMarkerStore(LocalObjectStore(tmp_path / "object-store")) + + marker_key = publish_environment_cache(built.root, store=store, prefix="environments") + + assert marker_key.endswith(f"keys/{request.environment_key}.json") + assert store.raced is True diff --git a/tests/test_execution_v2.py b/tests/test_execution_v2.py new file mode 100644 index 0000000..047e751 --- /dev/null +++ b/tests/test_execution_v2.py @@ -0,0 +1,460 @@ +from __future__ import annotations + +import json +import os +import sys +import time +from pathlib import Path + +import pytest + +from imread_benchmark.datasets.materializer import LocalObjectStore +from imread_benchmark.datasets.package import build_dataset_package, open_dataset_package +from imread_benchmark.environments import EnvironmentDescriptor, write_environment_descriptor +from imread_benchmark.execution.coordinator import ( + AttemptStatus, + CoordinatorConfig, + RemoteCheckpoint, + execute_run_specs, +) +from imread_benchmark.execution.spec import RunIdentity, RunSpec, RunSpecError, load_run_spec, write_run_spec +from imread_benchmark.plans import RunConfiguration +from imread_benchmark.platforms import PlatformDescriptor, write_platform_descriptor +from imread_benchmark.support import SupportAudit, build_operational_support, write_support_set + + +def _configuration( # noqa: PLR0913 - compact factory keeps run-spec tests focused on the varying fields + *, + decoder_id: str = "fake", + package_id: str = "b" * 64, + manifest_id: str = "c" * 64, + requested_threads: int | None = None, + minimum_timed_seconds: float = 0.01, + protocol_id: str = "decode-memory", + num_workers: int | None = None, +) -> RunConfiguration: + is_loader = protocol_id == "loader-supply" + if is_loader and num_workers is None: + raise ValueError("loader-supply test configuration requires num_workers") + return RunConfiguration( + protocol_id=protocol_id, + decoder_id=decoder_id, + package_id=package_id, + manifest_id=manifest_id, + selection_id="d" * 64, + requested_threads=requested_threads, + num_workers=num_workers if is_loader else None, + batch_size=1 if is_loader else None, + prefetch_factor=1 if is_loader and num_workers else None, + persistent_workers=bool(is_loader and num_workers), + multiprocessing_start_method="spawn" if is_loader and num_workers else None, + logical_repeat_factor=1, + warmup_passes=1, + timed_passes_per_run=2, + minimum_timed_seconds=minimum_timed_seconds, + output_contract="normalized-rgb", + support_policy="operational", + ) + + +def _provenance(tmp_path: Path) -> tuple[Path, str, Path, str]: + environment = EnvironmentDescriptor.build( + dependency_group="mainstream", + lock_sha256="8" * 64, + project_sha256="9" * 64, + runner_revision="1" * 40, + python={"abi": "fixture-abi", "implementation": "cpython", "version": "3.12.0"}, + platform_tags=("fixture-platform",), + distributions=(("imread-benchmark", "0.2.0"), ("pillow", "fixture")), + native_backends={}, + ) + platform = PlatformDescriptor.build( + identity={"architecture": "fixture", "logical_cpu_count": 4, "machine_type": "fixture"}, + runtime={"kernel": "fixture"}, + ) + environment_path = write_environment_descriptor(tmp_path / "provenance" / "environment.json", environment) + platform_path = write_platform_descriptor(tmp_path / "provenance" / "platform.json", platform) + return environment_path, environment.environment_id, platform_path, platform.platform_id + + +def test_run_spec_pins_every_identity_input_and_round_trips(tmp_path: Path) -> None: + environment_path, environment_id, platform_path, platform_id = _provenance(tmp_path) + identity = RunIdentity( + plan_id="a" * 64, + platform_id=platform_id, + environment_id=environment_id, + runner_revision="1" * 40, + workload_id="mixed", + support_set_id="2" * 64, + support_item_ids=("item-1", "item-2"), + configuration=_configuration(requested_threads=4), + repetition=3, + block_position=7, + ) + spec = RunSpec.build( + identity=identity, + package_descriptor=tmp_path / "package.json", + support_set_path=tmp_path / "support-set.json", + environment_descriptor=environment_path, + platform_descriptor=platform_path, + ) + + path = write_run_spec(tmp_path / "spec.json", spec) + loaded = load_run_spec(path) + + assert loaded == spec + assert loaded.run_key == spec.run_key + assert len(spec.run_key) == 64 + configuration = spec.to_dict()["configuration"] + assert isinstance(configuration, dict) + assert configuration["requested_threads"] == 4 + assert spec.to_dict()["support_item_ids"] == ["item-1", "item-2"] + assert spec.to_dict()["support_set_path"] == str((tmp_path / "support-set.json").resolve()) + assert spec.to_dict()["environment_descriptor"] == str(environment_path) + assert spec.to_dict()["platform_descriptor"] == str(platform_path) + + +def test_run_spec_rejects_a_forged_run_key(tmp_path: Path) -> None: + environment_path, environment_id, platform_path, platform_id = _provenance(tmp_path) + identity = RunIdentity( + plan_id="a" * 64, + platform_id=platform_id, + environment_id=environment_id, + runner_revision="1" * 40, + workload_id="mixed", + support_set_id="2" * 64, + support_item_ids=("item-1",), + configuration=_configuration(), + repetition=0, + block_position=0, + ) + spec = RunSpec.build( + identity=identity, + package_descriptor=tmp_path / "package.json", + support_set_path=tmp_path / "support-set.json", + environment_descriptor=environment_path, + platform_descriptor=platform_path, + ) + document = spec.to_dict() + document["run_key"] = "9" * 64 + + with pytest.raises(RunSpecError, match="run_key"): + RunSpec.from_dict(document) + + +def _run_spec( + tmp_path: Path, + *, + decoder_id: str, + position: int, + requested_threads: int | None, +) -> RunSpec: + environment_path, environment_id, platform_path, platform_id = _provenance(tmp_path) + return RunSpec.build( + identity=RunIdentity( + plan_id="a" * 64, + platform_id=platform_id, + environment_id=environment_id, + runner_revision="1" * 40, + workload_id="mixed", + support_set_id="2" * 64, + support_item_ids=("item-1", "item-2"), + configuration=_configuration(decoder_id=decoder_id, requested_threads=requested_threads), + repetition=0, + block_position=position, + ), + package_descriptor=tmp_path / "package.json", + support_set_path=tmp_path / "support-set.json", + environment_descriptor=environment_path, + platform_descriptor=platform_path, + ) + + +def _support_set_path( + tmp_path: Path, + configuration: RunConfiguration, + item_ids: tuple[str, ...], +) -> tuple[Path, str]: + uses_worker_process = configuration.protocol_id == "loader-supply" and bool(configuration.num_workers) + audit = SupportAudit( + audit_id="7" * 64, + decoder_id=configuration.decoder_id, + manifest_id=configuration.manifest_id, + selection_id=configuration.selection_id, + process_context="dataloader" if uses_worker_process else "main-process", + multiprocessing_start_method=configuration.multiprocessing_start_method, + requested_threads=configuration.requested_threads, + output_contract="normalized-rgb", + environment_id="f" * 64, + platform_id="e" * 64, + successful_item_ids=item_ids, + failures=(), + ) + support_set = build_operational_support(audit) + return write_support_set(tmp_path / "support", support_set), support_set.support_set_id + + +def test_coordinator_uses_fresh_processes_isolates_crash_and_resumes(tmp_path: Path) -> None: + specs = ( + _run_spec(tmp_path, decoder_id="fake", position=0, requested_threads=4), + _run_spec(tmp_path, decoder_id="hard-crash", position=1, requested_threads=8), + _run_spec(tmp_path, decoder_id="fake", position=2, requested_threads=None), + ) + fake_worker = Path(__file__).parent / "fixtures" / "fake_run_worker.py" + artifact_root = tmp_path / "artifacts" + + results = execute_run_specs( + specs, + CoordinatorConfig( + artifact_root=artifact_root, + attempts_root=tmp_path / "attempts", + timeout_seconds=10, + worker_command=(sys.executable, str(fake_worker)), + ), + ) + + assert [result.status for result in results] == [ + AttemptStatus.COMPLETED, + AttemptStatus.FAILED, + AttemptStatus.COMPLETED, + ] + runtimes = [ + json.loads((artifact_root / "runs" / spec.run_key / "runtime.json").read_text()) + for spec in (specs[0], specs[2]) + ] + assert runtimes[0]["process_id"] != runtimes[1]["process_id"] + assert runtimes[0]["effective_threads"] == 4 + assert runtimes[1]["effective_threads"] == 17 + failed_attempt = results[1].attempt_directory + assert failed_attempt is not None + assert (failed_attempt / "status.json").is_file() + assert not (artifact_root / "runs" / specs[1].run_key).exists() + + completed_mtime = (artifact_root / "runs" / specs[0].run_key / "COMMITTED.json").stat().st_mtime_ns + resumed = execute_run_specs( + specs, + CoordinatorConfig( + artifact_root=artifact_root, + attempts_root=tmp_path / "attempts", + timeout_seconds=10, + worker_command=(sys.executable, str(fake_worker)), + ), + ) + assert [result.status for result in resumed] == [ + AttemptStatus.SKIPPED, + AttemptStatus.FAILED, + AttemptStatus.SKIPPED, + ] + assert (artifact_root / "runs" / specs[0].run_key / "COMMITTED.json").stat().st_mtime_ns == completed_mtime + + +def test_coordinator_resumes_committed_runs_on_a_fresh_machine(tmp_path: Path) -> None: + specs = tuple( + _run_spec(tmp_path, decoder_id="fake", position=position, requested_threads=None) for position in range(3) + ) + fake_worker = Path(__file__).parent / "fixtures" / "fake_run_worker.py" + store = LocalObjectStore(tmp_path / "object-store") + first_machine = tmp_path / "machine-one" + (first_result,) = execute_run_specs( + (specs[0],), + CoordinatorConfig( + artifact_root=first_machine, + attempts_root=tmp_path / "attempts-one", + timeout_seconds=10, + worker_command=(sys.executable, str(fake_worker)), + remote=RemoteCheckpoint(store), + ), + ) + assert first_result.status is AttemptStatus.COMPLETED + first_bundle = first_machine / "runs" / specs[0].run_key + first_bundle_id = json.loads((first_bundle / "COMMITTED.json").read_text())["bundle_id"] + + second_machine = tmp_path / "machine-two" + resumed = execute_run_specs( + specs, + CoordinatorConfig( + artifact_root=second_machine, + attempts_root=tmp_path / "attempts-two", + timeout_seconds=10, + worker_command=(sys.executable, str(fake_worker)), + remote=RemoteCheckpoint(store), + ), + ) + + assert [result.status for result in resumed] == [ + AttemptStatus.SKIPPED, + AttemptStatus.COMPLETED, + AttemptStatus.COMPLETED, + ] + resumed_bundle_id = json.loads( + (second_machine / "runs" / specs[0].run_key / "COMMITTED.json").read_text(), + )["bundle_id"] + assert resumed_bundle_id == first_bundle_id + + +def test_coordinator_timeout_terminates_the_worker_process_group(tmp_path: Path) -> None: + spec = _run_spec(tmp_path, decoder_id="slow", position=0, requested_threads=None) + fake_worker = Path(__file__).parent / "fixtures" / "fake_run_worker.py" + + (result,) = execute_run_specs( + (spec,), + CoordinatorConfig( + artifact_root=tmp_path / "artifacts", + attempts_root=tmp_path / "attempts", + timeout_seconds=0.2, + worker_command=(sys.executable, str(fake_worker)), + ), + ) + + assert result.status is AttemptStatus.TIMED_OUT + assert result.attempt_directory is not None + assert (result.attempt_directory / "heartbeat.json").is_file() + child = json.loads((result.attempt_directory / "stdout.log").read_text()) + child_pid = child["child_process_id"] + deadline = time.monotonic() + 2 + while time.monotonic() < deadline: + try: + os.kill(child_pid, 0) + except ProcessLookupError: + break + time.sleep(0.05) + else: + pytest.fail(f"child process {child_pid} survived coordinator timeout") + assert not (tmp_path / "artifacts" / "runs" / spec.run_key).exists() + + +def test_production_worker_runs_decode_memory_from_resident_tar_bytes(tmp_path: Path, jpeg_dir: Path) -> None: + descriptor_path = build_dataset_package( + package_name="worker-fixture", + workloads={"fixture": jpeg_dir}, + output_root=tmp_path / "packages", + provenance={"source": "pytest"}, + ) + package = open_dataset_package(descriptor_path) + workload = package.descriptor["workloads"]["fixture"] + assert isinstance(workload, dict) + package_id = package.descriptor["package_id"] + manifest_id = workload["manifest_id"] + assert isinstance(package_id, str) + assert isinstance(manifest_id, str) + item_ids = tuple(item.item_id for item in package.read_workload_items("fixture")) + configuration = _configuration( + decoder_id="pillow", + package_id=package_id, + manifest_id=manifest_id, + requested_threads=None, + minimum_timed_seconds=1e-9, + ) + support_set_path, support_set_id = _support_set_path(tmp_path, configuration, item_ids) + environment_path, environment_id, platform_path, platform_id = _provenance(tmp_path) + spec = RunSpec.build( + identity=RunIdentity( + plan_id="a" * 64, + platform_id=platform_id, + environment_id=environment_id, + runner_revision="1" * 40, + workload_id="fixture", + support_set_id=support_set_id, + support_item_ids=item_ids, + configuration=configuration, + repetition=0, + block_position=0, + ), + package_descriptor=descriptor_path, + support_set_path=support_set_path, + environment_descriptor=environment_path, + platform_descriptor=platform_path, + ) + + (result,) = execute_run_specs( + (spec,), + CoordinatorConfig( + artifact_root=tmp_path / "artifacts", + attempts_root=tmp_path / "attempts", + timeout_seconds=10, + ), + ) + + assert result.status is AttemptStatus.COMPLETED + bundle = tmp_path / "artifacts" / "runs" / spec.run_key + runtime = json.loads((bundle / "runtime.json").read_text()) + dataset = json.loads((bundle / "dataset.json").read_text()) + samples = (bundle / "samples.jsonl").read_text().splitlines() + assert runtime["process_id"] == result.process_id + assert dataset["ordered_item_ids"] == list(item_ids) + assert len(samples) == configuration.timed_passes_per_run + + +@pytest.mark.parametrize("num_workers", [0, 2]) +def test_production_worker_exercises_real_loader_profiles( + tmp_path: Path, + jpeg_dir: Path, + num_workers: int, +) -> None: + descriptor_path = build_dataset_package( + package_name="loader-worker-fixture", + workloads={"fixture": jpeg_dir}, + output_root=tmp_path / "packages", + provenance={"source": "pytest"}, + ) + package = open_dataset_package(descriptor_path) + workload = package.descriptor["workloads"]["fixture"] + assert isinstance(workload, dict) + package_id = package.descriptor["package_id"] + manifest_id = workload["manifest_id"] + assert isinstance(package_id, str) + assert isinstance(manifest_id, str) + item_ids = tuple(item.item_id for item in package.read_workload_items("fixture")) + configuration = _configuration( + decoder_id="pillow", + package_id=package_id, + manifest_id=manifest_id, + requested_threads=None, + minimum_timed_seconds=1e-9, + protocol_id="loader-supply", + num_workers=num_workers, + ) + support_set_path, support_set_id = _support_set_path(tmp_path, configuration, item_ids) + environment_path, environment_id, platform_path, platform_id = _provenance(tmp_path) + spec = RunSpec.build( + identity=RunIdentity( + plan_id="a" * 64, + platform_id=platform_id, + environment_id=environment_id, + runner_revision="1" * 40, + workload_id="fixture", + support_set_id=support_set_id, + support_item_ids=item_ids, + configuration=configuration, + repetition=0, + block_position=0, + ), + package_descriptor=descriptor_path, + support_set_path=support_set_path, + environment_descriptor=environment_path, + platform_descriptor=platform_path, + ) + + (result,) = execute_run_specs( + (spec,), + CoordinatorConfig( + artifact_root=tmp_path / "artifacts", + attempts_root=tmp_path / "attempts", + timeout_seconds=30, + ), + ) + + assert result.status is AttemptStatus.COMPLETED + bundle = tmp_path / "artifacts" / "runs" / spec.run_key + runtime = json.loads((bundle / "runtime.json").read_text()) + handshakes = runtime["worker_handshakes"] + expected_processes = max(1, num_workers) + assert len(handshakes) == expected_processes + assert len({row["process_id"] for row in handshakes}) == expected_processes + if num_workers == 0: + assert handshakes[0]["process_id"] == runtime["process_id"] + else: + assert all(row["process_id"] != runtime["process_id"] for row in handshakes) + assert all(row["effective_threads"] == 1 for row in handshakes) + assert all(row["multiprocessing_start_method"] for row in handshakes) + assert runtime["persistent_workers_reused"] is True diff --git a/tests/test_fodb_dataset.py b/tests/test_fodb_dataset.py new file mode 100644 index 0000000..edf7cc7 --- /dev/null +++ b/tests/test_fodb_dataset.py @@ -0,0 +1,143 @@ +from __future__ import annotations + +import zipfile +from pathlib import Path + +import pytest + +from imread_benchmark.datasets.fodb import ( + FODB_PROVENANCES, + _describe_jpeg, + complete_scene_ids, + prepare_fodb, + read_fodb_catalog, + select_scene_ids, +) +from imread_benchmark.datasets.package import open_dataset_package + + +def _write_archive( + path: Path, + jpeg_bytes: bytes, + *, + devices: tuple[str, ...], + scenes: tuple[str, ...], +) -> None: + with zipfile.ZipFile(path, "w", compression=zipfile.ZIP_STORED) as archive: + for device_id in devices: + device_dir = f"{device_id}_Test_Camera_1" + for provenance in FODB_PROVENANCES: + for scene_id in scenes: + archive.writestr( + f"{device_dir}/{provenance}/{device_id}_img_{provenance}_{scene_id}.jpg", + jpeg_bytes, + ) + archive.writestr("inspection/check_devices/not-a-corpus-image.jpg", jpeg_bytes) + + +def test_catalog_ignores_inspection_and_selects_complete_scenes(tmp_path: Path, jpeg_bytes: bytes) -> None: + archive = tmp_path / "part01.zip" + devices = ("D01", "D02") + _write_archive(archive, jpeg_bytes, devices=devices, scenes=("0001", "0002")) + + members = read_fodb_catalog([archive]) + complete = complete_scene_ids(members, expected_device_count=len(devices)) + + assert len(members) == 2 * len(devices) * len(FODB_PROVENANCES) + assert complete == ("0001", "0002") + assert select_scene_ids(complete, count=1, seed=7) == select_scene_ids(complete, count=1, seed=7) + + +def test_prepare_fodb_rejects_a_resident_workload_above_the_byte_budget( + tmp_path: Path, + jpeg_bytes: bytes, +) -> None: + archive = tmp_path / "part01.zip" + devices = ("D01", "D02") + _write_archive(archive, jpeg_bytes, devices=devices, scenes=("0001",)) + mixed_bytes = len(jpeg_bytes) * len(devices) * len(FODB_PROVENANCES) + + with pytest.raises(ValueError, match="resident compressed-byte budget"): + prepare_fodb( + [archive], + tmp_path / "selected", + scene_count=1, + compressed_byte_limit=mixed_bytes - 1, + expected_device_count=len(devices), + ) + + +def test_jpeg_descriptor_stops_at_end_of_image(jpeg_bytes: bytes) -> None: + descriptor = _describe_jpeg(jpeg_bytes + b"trailing-data\xff\x02\x00\x00") + + assert descriptor.parse_error is None + assert descriptor.scan_count == 1 + + +def test_prepare_fodb_extracts_once_and_builds_native_and_mixed_workloads( + tmp_path: Path, + jpeg_bytes: bytes, +) -> None: + archive = tmp_path / "part01.zip" + output = tmp_path / "selected" + devices = ("D01", "D02") + _write_archive(archive, jpeg_bytes, devices=devices, scenes=("0001", "0002")) + + descriptor_path = prepare_fodb( + [archive], + output, + scene_count=1, + seed=11, + compressed_byte_limit=1024**2, + expected_device_count=len(devices), + ) + package = open_dataset_package(descriptor_path) + payload = package.descriptor["provenance"] + + assert payload["selection"]["requested_scene_count"] == 1 + assert len(payload["selection"]["selected_scene_ids"]) == 1 + assert payload["workloads"]["FODB-native"]["num_items"] == len(devices) + assert payload["workloads"]["FODB-mixed"]["num_items"] == len(devices) * len(FODB_PROVENANCES) + assert len(payload["items"]) == len(devices) * len(FODB_PROVENANCES) + assert all(item["jpeg"]["width"] == 64 for item in payload["items"]) + assert all(item["crc32"] == item["archive_member_crc32"] for item in payload["items"]) + + selection_root = output / "selections" / payload["selection"]["selection_id"] + mixed_item = next((selection_root / "workloads" / "fodb-mixed").rglob("*.jpg")) + workload_relative = mixed_item.relative_to(selection_root / "workloads" / "fodb-mixed") + corpus_item = output / "corpus" / Path(*workload_relative.parts[1:]) + assert mixed_item.samefile(corpus_item) + assert len(package.read_workload_items("fodb-native")) == len(devices) + assert len(package.read_workload_items("fodb-mixed")) == len(devices) * len(FODB_PROVENANCES) + + +def test_prepare_fodb_selections_do_not_contaminate_each_other( + tmp_path: Path, + jpeg_bytes: bytes, +) -> None: + archive = tmp_path / "part01.zip" + output = tmp_path / "selected" + devices = ("D01", "D02") + _write_archive(archive, jpeg_bytes, devices=devices, scenes=("0001", "0002")) + + first = prepare_fodb( + [archive], + output, + scene_count=2, + seed=11, + compressed_byte_limit=1024**2, + expected_device_count=len(devices), + ) + second = prepare_fodb( + [archive], + output, + scene_count=1, + seed=11, + compressed_byte_limit=1024**2, + expected_device_count=len(devices), + ) + + assert first != second + first_items = open_dataset_package(first).read_workload_items("fodb-mixed") + assert len(first_items) == 2 * len(devices) * len(FODB_PROVENANCES) + assert len(open_dataset_package(second).read_workload_items("fodb-mixed")) == len(devices) * len(FODB_PROVENANCES) diff --git a/tests/test_fodb_paper.py b/tests/test_fodb_paper.py new file mode 100644 index 0000000..0228827 --- /dev/null +++ b/tests/test_fodb_paper.py @@ -0,0 +1,172 @@ +from __future__ import annotations + +import pytest + +from imread_benchmark.analysis.fodb_paper import ( + COMPATIBILITY_AUDIT_EMPTY_DHT_ITEM_COUNT, + COMPATIBILITY_AUDIT_EMPTY_DHT_SUCCESSES, + COMPATIBILITY_AUDIT_FOUR_COMPONENT_ITEM_COUNT, + COMPATIBILITY_AUDIT_FOUR_COMPONENT_SUCCESSES, + COMPATIBILITY_AUDIT_ITEM_COUNT, + COMPATIBILITY_AUDIT_SUCCESSES, + WORKER12_DECODERS, + WORKER12_PLAN_SCOPES, + WORKER16_DECODERS, + WORKER16_PLAN_SCOPES, + Aggregate, + _compatibility_audit, + _decoder_coverage_table, + _linear_quantile, + _ranks, + _worker16_candidate_rows, + _worker_transfer_table, + _workload_descriptors, +) + + +def _package_item(*, sha256: str, provenance: str, quality: int, progressive: bool) -> dict[str, object]: + return { + "bits_per_pixel": 1.5, + "jpeg": { + "megapixels": 2.0, + "progressive": progressive, + "quality_estimate": quality, + "subsampling": "4:2:0", + }, + "jpeg_bytes": 100, + "jpeg_sha256": sha256, + "provenance": provenance, + } + + +def test_workload_descriptors_distinguish_manifest_from_timed_common_support() -> None: + native = _package_item(sha256="native", provenance="orig", quality=95, progressive=False) + social = _package_item(sha256="social", provenance="whatsapp", quality=69, progressive=True) + package = {"provenance": {"items": [native, social]}} + manifests = { + "fodb-native": [{"item_id": "native-id", "sha256": "native"}], + "fodb-mixed": [ + {"item_id": "native-id", "sha256": "native"}, + {"item_id": "social-id", "sha256": "social"}, + ], + } + support = {"fodb-native": {"native-id"}, "fodb-mixed": {"native-id"}} + + descriptors = _workload_descriptors(package, manifests, support) + + native_row, mixed_row = descriptors + assert native_row["items"] == native_row["manifest_items"] == 1 + assert native_row["provenance"] == {"orig": 1} + assert mixed_row["items"] == 1 + assert mixed_row["manifest_items"] == 2 + assert mixed_row["provenance"] == {"orig": 1} + assert mixed_row["excluded_items"] == 1 + assert mixed_row["excluded_profile"] == { + "estimated_quality": {69.0: 1}, + "progressive_items": 1, + "provenance": {"whatsapp": 1}, + } + + +def test_rank_and_quantile_helpers_are_deterministic() -> None: + assert _ranks({"a": 3.0, "b": 2.0, "c": 2.0, "d": 1.0}) == { + "a": 1.0, + "b": 2.5, + "c": 2.5, + "d": 4.0, + } + assert _linear_quantile([1.0, 2.0, 3.0], 0.25) == pytest.approx(1.5) + + +def test_workers_12_followup_is_frozen_to_87_cells() -> None: + assert sum(len(decoders) for decoders in WORKER12_DECODERS.values()) == 87 + assert sum(len(decoders) * 5 for decoders in WORKER12_DECODERS.values()) == 435 + plan_scopes = { + cell: decoders for scoped_cells in WORKER12_PLAN_SCOPES.values() for cell, decoders in scoped_cells.items() + } + assert plan_scopes == WORKER12_DECODERS + + worker16_scopes = { + cell: decoders for scoped_cells in WORKER16_PLAN_SCOPES.values() for cell, decoders in scoped_cells.items() + } + assert worker16_scopes == {cell: decoders for cell, decoders in WORKER16_DECODERS.items() if decoders} + assert sum(len(decoders) for decoders in WORKER16_DECODERS.values()) == 58 + assert sum(len(decoders) * 5 for decoders in WORKER16_DECODERS.values()) == 290 + + +def test_workers_16_candidates_require_material_and_consistent_workers_12_gain() -> None: + aggregates = [] + means = { + "pillow": {0: 50.0, 2: 70.0, 4: 80.0, 8: 90.0, 12: 95.0}, + "opencv": {0: 50.0, 2: 70.0, 4: 80.0, 8: 90.0, 12: 85.0}, + "simplejpeg": {0: 50.0, 2: 70.0, 4: 80.0, 8: 90.0, 12: 92.0}, + "kornia": {0: 50.0, 2: 70.0, 4: 80.0, 8: 90.0, 12: 96.0}, + } + raw_run_means = {"kornia": (100.0, 100.0, 100.0, 100.0, 80.0)} + for decoder, worker_means in means.items(): + for workers, mean in worker_means.items(): + aggregates.append( + Aggregate( + workload="fodb-native", + machine_type="c4-standard-16", + protocol="loader-supply", + decoder=decoder, + requested_threads=1 if decoder == "opencv" else None, + workers=workers, + repetitions=(0, 1, 2, 3, 4), + raw_run_means=raw_run_means.get(decoder, (mean,) * 5) if workers == 12 else (mean,) * 5, + mean=mean, + sample_std=0.0, + ), + ) + + candidates = _worker16_candidate_rows( + tuple(aggregates), + {("fodb-native", "c4-standard-16"): ("pillow", "opencv", "simplejpeg", "kornia")}, + ) + + assert candidates["candidate_count"] == 1 + assert candidates["bundle_count_if_launched"] == 5 + assert candidates["minimum_mean_gain_percent"] == 5.0 + assert [row["decoder"] for row in candidates["cells"][0]["decoders"]] == ["pillow"] + + +def test_compatibility_audit_reports_bitstream_and_output_contract_separately() -> None: + audit = _compatibility_audit() + + assert COMPATIBILITY_AUDIT_ITEM_COUNT == ( + COMPATIBILITY_AUDIT_EMPTY_DHT_ITEM_COUNT + COMPATIBILITY_AUDIT_FOUR_COMPONENT_ITEM_COUNT + ) + assert set(COMPATIBILITY_AUDIT_SUCCESSES) == set(COMPATIBILITY_AUDIT_EMPTY_DHT_SUCCESSES) + assert set(COMPATIBILITY_AUDIT_SUCCESSES) == set(COMPATIBILITY_AUDIT_FOUR_COMPONENT_SUCCESSES) + for decoder, combined_successes in COMPATIBILITY_AUDIT_SUCCESSES.items(): + assert combined_successes == ( + COMPATIBILITY_AUDIT_EMPTY_DHT_SUCCESSES[decoder] + COMPATIBILITY_AUDIT_FOUR_COMPONENT_SUCCESSES[decoder] + ) + + assert audit["item_count"] == COMPATIBILITY_AUDIT_ITEM_COUNT + assert audit["successes"] == COMPATIBILITY_AUDIT_SUCCESSES + table = _decoder_coverage_table(audit) + + assert r"\texttt{ajpegli} & 0/276 & 0/1 & 0/277" in table + assert r"\texttt{torchvision} & 276/276 & 0/1 & 276/277" in table + assert r"\texttt{simplejpeg} & 276/276 & 1/1 & 277/277" in table + + +def test_worker_transfer_table_keeps_counts_readable_and_compact() -> None: + table = _worker_transfer_table( + [ + { + "changed_decoders": [f"decoder-{index}" for index in range(11)], + "mixed_peak_worker_counts": {12: 1, 16: 11}, + "native_peak_worker_counts": {0: 4, 12: 7, 16: 1}, + "platform": "Intel 8581C", + }, + ], + ) + + assert "0: 4, 12: 7, 16: 1" in table + assert "12: 1, 16: 11" in table + assert "11/12" in table + assert table.count(" & ") == 3 + assert r"\texttt" not in table diff --git a/tests/test_gcp_bootstrap.py b/tests/test_gcp_bootstrap.py new file mode 100644 index 0000000..da33e40 --- /dev/null +++ b/tests/test_gcp_bootstrap.py @@ -0,0 +1,38 @@ +from __future__ import annotations + +import subprocess +from pathlib import Path + + +def test_gcp_shell_is_syntax_valid_and_delegates_benchmark_semantics_to_python() -> None: + root = Path(__file__).parents[1] + scripts = (root / "gcp" / "run.sh", root / "gcp" / "run-many.sh", root / "gcp" / "vm_startup.sh") + native_installer = root / "scripts" / "install-libjpeg-turbo.sh" + + for script in (*scripts, native_installer): + subprocess.run(("/bin/bash", "-n", str(script)), check=True) # noqa: S603 + + startup = scripts[-1].read_text() + assert "dataset materialize" in startup + assert "environment provision" in startup + assert "campaign run" in startup + assert "DONE.json" in startup + assert "ulimit -n 65535" in startup + assert "/snap/google-cloud-cli/current/bin/gcloud" in startup + + launcher = scripts[0].read_text() + assert 'MACHINE_TYPE="${MACHINE_TYPE:-c4-standard-16}"' in launcher + assert "c3-standard-16" not in launcher + + installer = native_installer.read_text() + assert "LIBJPEG_TURBO_VERSION=" in installer + assert "libjpeg-turbo-official_${LIBJPEG_TURBO_VERSION}_amd64.deb" in installer + assert "libjpeg-turbo-official_${LIBJPEG_TURBO_VERSION}_arm64.deb" in installer + assert "sha256sum --check --strict" in installer + assert "scripts/install-libjpeg-turbo.sh" in startup + assert "libjpeg-turbo8-dev" not in startup + assert '--native-backend "libjpeg-turbo=$LIBJPEG_TURBO_BACKEND"' in startup + + project = (root / "pyproject.toml").read_text() + assert '"pyturbojpeg",' in project + assert '"pyturbojpeg<2"' not in project diff --git a/tests/test_materializer.py b/tests/test_materializer.py new file mode 100644 index 0000000..5d2a60b --- /dev/null +++ b/tests/test_materializer.py @@ -0,0 +1,101 @@ +from __future__ import annotations + +import sys +from pathlib import Path + +import pytest + +from imread_benchmark.datasets.gcs import GcloudObjectStore +from imread_benchmark.datasets.materializer import ( + LocalObjectStore, + MaterializationError, + materialize_dataset_package, + publish_dataset_package, +) +from imread_benchmark.datasets.package import build_dataset_package, open_dataset_package + + +def test_package_publish_and_materialize_is_verified_atomic_and_reusable( + tmp_path: Path, + jpeg_dir: Path, +) -> None: + descriptor_path = build_dataset_package( + package_name="materializer-fixture", + workloads={"fixture": jpeg_dir}, + output_root=tmp_path / "source-packages", + provenance={"source": "pytest"}, + ) + store = LocalObjectStore(tmp_path / "object-store") + remote_descriptor = publish_dataset_package(descriptor_path, store=store, prefix="datasets") + + materialized_descriptor = materialize_dataset_package( + remote_descriptor, + store=store, + cache_root=tmp_path / "cache", + ) + package = open_dataset_package(materialized_descriptor, trust_ready=True) + archive_path = package.root / package.descriptor["archive"]["file"] + archive_mtime = archive_path.stat().st_mtime_ns + + assert (package.root / ".READY.json").is_file() + assert (package.root / ".CONTENT_VERIFIED" / "fixture.json").is_file() + assert not list(package.root.rglob("*.jpg")) + assert len(package.read_workload_items("fixture")) == 4 + + reused = materialize_dataset_package(remote_descriptor, store=store, cache_root=tmp_path / "cache") + assert reused == materialized_descriptor + assert archive_path.stat().st_mtime_ns == archive_mtime + + +def test_materializer_rejects_corrupt_remote_archive_without_publishing_partial_package( + tmp_path: Path, + jpeg_dir: Path, +) -> None: + descriptor_path = build_dataset_package( + package_name="corrupt-materializer-fixture", + workloads={"fixture": jpeg_dir}, + output_root=tmp_path / "source-packages", + provenance={"source": "pytest"}, + ) + store = LocalObjectStore(tmp_path / "object-store") + remote_descriptor = publish_dataset_package(descriptor_path, store=store, prefix="datasets") + descriptor = open_dataset_package(descriptor_path).descriptor + package_id = descriptor["package_id"] + archive_name = descriptor["archive"]["file"] + assert isinstance(package_id, str) + assert isinstance(archive_name, str) + remote_archive = tmp_path / "object-store" / "datasets" / package_id / archive_name + remote_archive.write_bytes(remote_archive.read_bytes()[:-1] + b"x") + + with pytest.raises(MaterializationError, match="checksum"): + materialize_dataset_package(remote_descriptor, store=store, cache_root=tmp_path / "cache") + assert not (tmp_path / "cache" / package_id).exists() + + +def test_gcloud_adapter_exercises_create_only_upload_and_download_via_real_subprocess( + tmp_path: Path, + jpeg_dir: Path, +) -> None: + descriptor_path = build_dataset_package( + package_name="gcloud-adapter-fixture", + workloads={"fixture": jpeg_dir}, + output_root=tmp_path / "source-packages", + provenance={"source": "pytest"}, + ) + fake_gcloud = Path(__file__).parent / "fixtures" / "fake_gcloud.py" + store = GcloudObjectStore( + "gs://test-bucket/benchmark", + command_prefix=(sys.executable, str(fake_gcloud)), + environment={"FAKE_GCS_ROOT": str(tmp_path / "fake-gcs")}, + ) + + remote_descriptor = publish_dataset_package(descriptor_path, store=store, prefix="datasets") + publish_dataset_package(descriptor_path, store=store, prefix="datasets") + materialized = materialize_dataset_package( + remote_descriptor, + store=store, + cache_root=tmp_path / "cache", + ) + + package = open_dataset_package(materialized, trust_ready=True) + assert len(package.read_workload_items("fixture")) == 4 diff --git a/tests/test_memory_budget.py b/tests/test_memory_budget.py new file mode 100644 index 0000000..a40d9f0 --- /dev/null +++ b/tests/test_memory_budget.py @@ -0,0 +1,37 @@ +from __future__ import annotations + +from imread_benchmark.execution.memory import estimate_peak_memory +from imread_benchmark.plans import RunConfiguration + + +def test_spawn_memory_estimate_counts_resident_replicas_and_prefetched_rgb_batches() -> None: + configuration = RunConfiguration( + protocol_id="loader-supply", + decoder_id="pillow", + package_id="a" * 64, + manifest_id="b" * 64, + selection_id="c" * 64, + requested_threads=None, + num_workers=2, + batch_size=1, + prefetch_factor=1, + persistent_workers=True, + multiprocessing_start_method="spawn", + logical_repeat_factor=1, + warmup_passes=1, + timed_passes_per_run=2, + minimum_timed_seconds=0.1, + output_contract="normalized-rgb", + support_policy="common", + ) + items = ( + {"item_id": "one", "compressed_bytes": 100, "width": 10, "height": 20}, + {"item_id": "two", "compressed_bytes": 200, "width": 5, "height": 5}, + ) + + estimate = estimate_peak_memory(configuration, items) + + assert estimate.resident_compressed_bytes == 300 + assert estimate.resident_replica_count == 3 + assert estimate.decoded_inflight_bytes == 3_000 + assert estimate.estimated_peak_bytes == 3_900 diff --git a/tests/test_paper_assets.py b/tests/test_paper_assets.py deleted file mode 100644 index cb73cdf..0000000 --- a/tests/test_paper_assets.py +++ /dev/null @@ -1,244 +0,0 @@ -from __future__ import annotations - -from pathlib import Path - -import pandas as pd -import pytest - -from tools import paper_assets -from tools._results import load_dataloader_results, load_results -from tools.paper_assets import ( - EXPECTED_DATALOADER_DECODERS, - EXPECTED_SINGLE_DECODERS, - EXPECTED_SKIP_DECODERS, - PAPER_PLATFORMS, - ROBUSTNESS_DECODERS, - WORKERS_ORDER, - _latex_table_row, - _platform_recommendation_choices, - _platform_recommendation_rows, - generate_platform_recommendation_table, - generate_robustness_table, - validate_paper_data, -) - - -def _repo_output() -> Path: - root = Path("output") - if not root.exists(): - pytest.skip("paper benchmark output/ directory is not present") - return root - - -def _valid_single_thread_frame() -> pd.DataFrame: - rows = [] - for platform in PAPER_PLATFORMS: - rows.extend( - { - "platform": platform, - "library": library, - "run_tag": "1t", - "num_images_skipped": 1 if library in EXPECTED_SKIP_DECODERS else 0, - } - for library in EXPECTED_SINGLE_DECODERS - ) - return pd.DataFrame(rows) - - -def _valid_dataloader_frame() -> pd.DataFrame: - rows = [] - for platform in PAPER_PLATFORMS: - for library in EXPECTED_DATALOADER_DECODERS: - rows.extend( - { - "platform": platform, - "library": library, - "num_workers": worker_count, - } - for worker_count in WORKERS_ORDER - ) - return pd.DataFrame(rows) - - -def test_paper_data_matrix_matches_claimed_scope() -> None: - root = _repo_output() - summary = validate_paper_data(load_results(root), load_dataloader_results(root)) - - assert summary["platforms"] == 5 - assert summary["single_thread_decoders"] == 13 - assert summary["dataloader_decoders"] == 11 - assert summary["single_thread_rows"] == 65 - assert summary["dataloader_worker_rows"] == 220 - - -def test_validate_paper_data_missing_platform_raises() -> None: - single = _valid_single_thread_frame() - dl = _valid_dataloader_frame() - missing_platform = PAPER_PLATFORMS[0] - - with pytest.raises(ValueError, match="DataLoader platform mismatch"): - validate_paper_data(single, dl[dl["platform"] != missing_platform]) - - -def test_validate_paper_data_missing_single_thread_decoder_raises() -> None: - single = _valid_single_thread_frame() - dl = _valid_dataloader_frame() - missing_library = next(iter(EXPECTED_SINGLE_DECODERS)) - - with pytest.raises(ValueError, match="single-thread decoder mismatch"): - validate_paper_data(single[single["library"] != missing_library], dl) - - -def test_validate_paper_data_extra_unknown_decoder_raises() -> None: - single = _valid_single_thread_frame() - dl = _valid_dataloader_frame() - extra_row = { - "platform": PAPER_PLATFORMS[0], - "library": "unknown-decoder", - "num_workers": WORKERS_ORDER[0], - } - dl = pd.concat([dl, pd.DataFrame([extra_row])], ignore_index=True) - - with pytest.raises(ValueError, match="DataLoader decoder mismatch"): - validate_paper_data(single, dl) - - -def test_validate_paper_data_incorrect_num_workers_raises() -> None: - single = _valid_single_thread_frame() - dl = _valid_dataloader_frame() - library = next(iter(EXPECTED_DATALOADER_DECODERS)) - row_mask = (dl["platform"] == PAPER_PLATFORMS[0]) & (dl["library"] == library) - row_mask &= dl["num_workers"] == WORKERS_ORDER[0] - dl.loc[row_mask, "num_workers"] = 999 - - with pytest.raises(ValueError, match="worker mismatch"): - validate_paper_data(single, dl) - - -def test_validate_paper_data_incorrect_num_images_skipped_raises() -> None: - single = _valid_single_thread_frame() - dl = _valid_dataloader_frame() - skipped_library = next(iter(EXPECTED_SKIP_DECODERS)) - row_mask = (single["platform"] == PAPER_PLATFORMS[0]) & (single["library"] == skipped_library) - single.loc[row_mask, "num_images_skipped"] = 2 - - with pytest.raises(ValueError, match="skip counts must be 1"): - validate_paper_data(single, dl) - - -def test_robustness_table_surfaces_only_observed_skip_decoders(tmp_path: Path) -> None: - root = _repo_output() - dest = tmp_path / "table05_robustness.md" - generate_robustness_table(root, load_results(root), dest) - - text = dest.read_text(encoding="utf-8") - lines = text.splitlines() - for decoder in ROBUSTNESS_DECODERS: - decoder_label = f"`{decoder}`" - matching_line = next((line for line in lines if decoder_label in line), None) - assert matching_line is not None, f"Decoder {decoder!r} not found in robustness table." - - expected_prefix = "1 / 50,000" if decoder in EXPECTED_SKIP_DECODERS else "0 / 50,000" - assert expected_prefix in matching_line - - -def test_platform_recommendation_table_has_three_zero_skip_choices_per_platform(tmp_path: Path) -> None: - root = _repo_output() - dest = tmp_path / "table07_platform_recommendations.tex" - generate_platform_recommendation_table(load_dataloader_results(root), dest) - - text = dest.read_text(encoding="utf-8") - data_rows = [line for line in text.splitlines() if "GCP \\texttt" in line] - assert len(data_rows) == len(PAPER_PLATFORMS) - assert all(row.count("img/s") == 3 for row in data_rows) - - for decoder in EXPECTED_SKIP_DECODERS: - assert f"\\texttt{{{decoder}}}:" not in text - assert r"\texttt{pyvips}:" not in text - assert r"\texttt{tensorflow}:" not in text - - choices_by_platform = _platform_recommendation_choices(load_dataloader_results(root)) - assert [platform for platform, _ in choices_by_platform] == list(PAPER_PLATFORMS) - for _, choices in choices_by_platform: - assert len(choices) == 3 - assert choices["library"].is_unique - assert choices["peak_ips"].tolist() == sorted(choices["peak_ips"], reverse=True) - assert choices["peak_workers"].isin(WORKERS_ORDER).all() - - -def test_latex_table_row_formats_cells_with_indentation_and_linebreak() -> None: - assert _latex_table_row(["A", "B", "C"]) == r" A & B & C \\" - - -def test_platform_recommendation_rows_requires_three_zero_skip_choices() -> None: - platform = PAPER_PLATFORMS[0] - dl = pd.DataFrame( - [ - { - "platform": platform, - "library": "opencv", - "num_workers": 0, - "images_per_second": 100.0, - "cpu_brand": "Synthetic CPU", - }, - { - "platform": platform, - "library": "imageio", - "num_workers": 2, - "images_per_second": 90.0, - "cpu_brand": "Synthetic CPU", - }, - ], - ) - - with pytest.raises(ValueError, match="fewer than three zero-skip DataLoader choices"): - _platform_recommendation_rows(dl) - - -def test_platform_recommendation_choices_ignore_skip_decoders(monkeypatch: pytest.MonkeyPatch) -> None: - platform = PAPER_PLATFORMS[0] - monkeypatch.setattr(paper_assets, "PAPER_PLATFORMS", (platform,)) - dl = pd.DataFrame( - [ - { - "platform": platform, - "library": "jpeg4py", - "num_workers": 8, - "images_per_second": 500.0, - "cpu_brand": "Synthetic CPU", - }, - { - "platform": platform, - "library": "turbojpeg", - "num_workers": 8, - "images_per_second": 450.0, - "cpu_brand": "Synthetic CPU", - }, - { - "platform": platform, - "library": "opencv", - "num_workers": 4, - "images_per_second": 300.0, - "cpu_brand": "Synthetic CPU", - }, - { - "platform": platform, - "library": "imageio", - "num_workers": 2, - "images_per_second": 200.0, - "cpu_brand": "Synthetic CPU", - }, - { - "platform": platform, - "library": "pillow", - "num_workers": 0, - "images_per_second": 100.0, - "cpu_brand": "Synthetic CPU", - }, - ], - ) - - [(selected_platform, choices)] = _platform_recommendation_choices(dl) - - assert selected_platform == platform - assert choices["library"].tolist() == ["opencv", "imageio", "pillow"] diff --git a/tests/test_plan_v2.py b/tests/test_plan_v2.py new file mode 100644 index 0000000..6aa691d --- /dev/null +++ b/tests/test_plan_v2.py @@ -0,0 +1,345 @@ +from __future__ import annotations + +import json +from pathlib import Path + +import pytest + +from imread_benchmark.datasets.package import build_dataset_package +from imread_benchmark.plans import ( + PlanError, + RunConfiguration, + expand_experiment_plan, + instantiate_experiment_plans, + load_experiment_plan, +) + + +def test_plan_pins_exact_package_workload_manifest_and_selection( + tmp_path: Path, + jpeg_dir: Path, +) -> None: + descriptor_path = build_dataset_package( + package_name="fixture-jpegs", + workloads={"fixture": jpeg_dir}, + output_root=tmp_path / "packages", + provenance={"source": "pytest"}, + ) + descriptor = json.loads(descriptor_path.read_text()) + plan_path = tmp_path / "experiment.yaml" + plan_path.write_text(_plan_yaml(descriptor_path, descriptor)) + + plan = load_experiment_plan(plan_path) + workload = descriptor["workloads"]["fixture"] + + assert plan.schema_version == "2.0" + assert plan.dataset.package_id == descriptor["package_id"] + assert plan.dataset.manifest_id == workload["manifest_id"] + assert plan.dataset.selection.item_ids == tuple(item["item_id"] for item in plan.dataset.manifest["items"]) + assert plan.dataset.selection.selection_id + + +def test_plan_requires_one_fresh_subprocess_per_run(tmp_path: Path, jpeg_dir: Path) -> None: + descriptor_path = build_dataset_package( + package_name="fixture-jpegs", + workloads={"fixture": jpeg_dir}, + output_root=tmp_path / "packages", + provenance={"source": "pytest"}, + ) + descriptor = json.loads(descriptor_path.read_text()) + plan_path = tmp_path / "experiment.yaml" + plan_path.write_text( + _plan_yaml(descriptor_path, descriptor).replace("per_run_subprocess: true", "per_run_subprocess: false"), + ) + + with pytest.raises(PlanError, match="per_run_subprocess"): + load_experiment_plan(plan_path) + + +def test_plan_requires_an_explicit_memory_fraction(tmp_path: Path, jpeg_dir: Path) -> None: + descriptor_path = build_dataset_package( + package_name="fixture-jpegs", + workloads={"fixture": jpeg_dir}, + output_root=tmp_path / "packages", + provenance={"source": "pytest"}, + ) + descriptor = json.loads(descriptor_path.read_text()) + plan_path = tmp_path / "experiment.yaml" + plan_path.write_text(_plan_yaml(descriptor_path, descriptor).replace(" maximum_memory_fraction: 0.6\n", "")) + + with pytest.raises(PlanError, match="maximum_memory_fraction"): + load_experiment_plan(plan_path) + + +def test_plan_rejects_requested_threads_for_a_fixed_thread_decoder(tmp_path: Path, jpeg_dir: Path) -> None: + descriptor_path = build_dataset_package( + package_name="fixture-jpegs", + workloads={"fixture": jpeg_dir}, + output_root=tmp_path / "packages", + provenance={"source": "pytest"}, + ) + descriptor = json.loads(descriptor_path.read_text()) + plan_path = tmp_path / "experiment.yaml" + plan_path.write_text(_plan_yaml(descriptor_path, descriptor).replace("threads: [default]", "threads: [1]")) + + with pytest.raises(PlanError, match="thread control"): + expand_experiment_plan(load_experiment_plan(plan_path)) + + +def test_plan_accepts_a_verified_materialized_descriptor_override(tmp_path: Path, jpeg_dir: Path) -> None: + descriptor_path = build_dataset_package( + package_name="fixture-jpegs", + workloads={"fixture": jpeg_dir}, + output_root=tmp_path / "packages", + provenance={"source": "pytest"}, + ) + descriptor = json.loads(descriptor_path.read_text()) + plan_path = tmp_path / "experiment.yaml" + plan_text = _plan_yaml(descriptor_path, descriptor).replace(str(descriptor_path), "unavailable/package.json") + plan_path.write_text(plan_text) + + plan = load_experiment_plan(plan_path, dataset_descriptor=descriptor_path) + + assert plan.dataset.descriptor_path == descriptor_path.resolve() + + +def test_plan_instantiation_fills_and_validates_every_package_workload( + tmp_path: Path, + jpeg_dir: Path, +) -> None: + descriptor_path = build_dataset_package( + package_name="fixture-jpegs", + workloads={"native": jpeg_dir, "mixed": jpeg_dir}, + output_root=tmp_path / "packages", + provenance={"source": "pytest"}, + ) + template = Path(__file__).parents[1] / "examples" / "fodb-experiment.template.yaml" + + instantiated = instantiate_experiment_plans( + template_path=template, + package_descriptor=descriptor_path, + output_dir=tmp_path / "plans", + ) + + assert tuple(item.workload_id for item in instantiated) == ("mixed", "native") + assert all(item.run_count > 0 for item in instantiated) + assert all(item.path.is_file() for item in instantiated) + for item in instantiated: + document = item.path.read_text() + assert "PACKAGE_ID" not in document + assert "WORKLOAD_ID" not in document + assert "MANIFEST_ID" not in document + assert "ITEM_COUNT" not in document + plan = load_experiment_plan(item.path, dataset_descriptor=descriptor_path) + assert plan.dataset.workload_id == item.workload_id + assert expand_experiment_plan(plan)[0].plan_id == item.plan_id + + +def test_plan_instantiation_preserves_existing_output_when_validation_fails( + tmp_path: Path, + jpeg_dir: Path, +) -> None: + descriptor_path = build_dataset_package( + package_name="fixture-jpegs", + workloads={"fixture": jpeg_dir}, + output_root=tmp_path / "packages", + provenance={"source": "pytest"}, + ) + source_template = Path(__file__).parents[1] / "examples" / "fodb-experiment.template.yaml" + invalid_template = tmp_path / "invalid.template.yaml" + invalid_template.write_text( + source_template.read_text().replace("per_run_subprocess: true", "per_run_subprocess: false"), + ) + output_dir = tmp_path / "plans" + output_dir.mkdir() + existing = output_dir / "fixture.yaml" + existing.write_text("previous plan\n") + + with pytest.raises(PlanError, match="per_run_subprocess"): + instantiate_experiment_plans( + template_path=invalid_template, + package_descriptor=descriptor_path, + output_dir=output_dir, + ) + + assert existing.read_text() == "previous plan\n" + + +def test_fodb_smoke_template_expands_to_nine_runs(tmp_path: Path, jpeg_dir: Path) -> None: + descriptor_path = build_dataset_package( + package_name="fixture-jpegs", + workloads={"fixture": jpeg_dir}, + output_root=tmp_path / "packages", + provenance={"source": "pytest"}, + ) + + instantiated = instantiate_experiment_plans( + template_path=Path(__file__).parents[1] / "examples" / "fodb-smoke.template.yaml", + package_descriptor=descriptor_path, + output_dir=tmp_path / "plans", + ) + + assert instantiated[0].run_count == 9 + + +@pytest.mark.parametrize( + "template_name", + [ + "controlled-ablation.template.yaml", + "fodb-experiment.template.yaml", + "fodb-tensorflow.template.yaml", + ], +) +def test_checked_in_plan_templates_expand_against_a_real_package( + tmp_path: Path, + jpeg_dir: Path, + template_name: str, +) -> None: + descriptor_path = build_dataset_package( + package_name="fixture-jpegs", + workloads={"fixture": jpeg_dir}, + output_root=tmp_path / "packages", + provenance={"source": "pytest"}, + ) + template_path = Path(__file__).parents[1] / "examples" / template_name + instantiated = instantiate_experiment_plans( + template_path=template_path, + package_descriptor=descriptor_path, + output_dir=tmp_path / "plans", + workload_ids=("fixture",), + ) + templates = expand_experiment_plan( + load_experiment_plan(instantiated[0].path, dataset_descriptor=descriptor_path), + ) + + assert templates + assert {template.configuration.package_id for template in templates} == { + json.loads(descriptor_path.read_text())["package_id"], + } + + +def test_plan_expands_worker_profiles_in_seeded_repetition_blocks(tmp_path: Path, jpeg_dir: Path) -> None: + descriptor_path = build_dataset_package( + package_name="fixture-jpegs", + workloads={"fixture": jpeg_dir}, + output_root=tmp_path / "packages", + provenance={"source": "pytest"}, + ) + descriptor = json.loads(descriptor_path.read_text()) + plan_path = tmp_path / "experiment.yaml" + plan_path.write_text( + _plan_yaml(descriptor_path, descriptor).replace( + " decode-memory: {}", + """\ + decode-memory: {} + loader-supply: + worker_profiles: + - workers: [0] + batch_size: 1 + - workers: [2, 4] + batch_size: 1 + multiprocessing_start_method: spawn + prefetch_factor: 1 + persistent_workers: true""", + ), + ) + + plan = load_experiment_plan(plan_path) + first = expand_experiment_plan(plan) + second = expand_experiment_plan(plan) + + assert first == second + assert len(first) == 8 # 1 decode + 3 loader configurations, repeated twice + assert [template.position for template in first] == list(range(8)) + for repetition in range(2): + block = [template for template in first if template.repetition == repetition] + assert len(block) == 4 + assert len({template.configuration.config_id for template in block}) == 4 + zero_worker = next(template.configuration for template in first if template.configuration.num_workers == 0) + assert zero_worker.prefetch_factor is None + assert zero_worker.persistent_workers is False + assert zero_worker.multiprocessing_start_method is None + process_worker = next(template.configuration for template in first if template.configuration.num_workers == 2) + assert process_worker.multiprocessing_start_method == "spawn" + + +def test_run_configuration_rejects_protocol_fields_that_do_not_apply() -> None: + with pytest.raises(ValueError, match="decode-memory"): + RunConfiguration( + protocol_id="decode-memory", + decoder_id="pillow", + package_id="a" * 64, + manifest_id="b" * 64, + selection_id="c" * 64, + requested_threads=None, + num_workers=2, + batch_size=1, + prefetch_factor=1, + persistent_workers=True, + multiprocessing_start_method="spawn", + logical_repeat_factor=1, + warmup_passes=1, + timed_passes_per_run=2, + minimum_timed_seconds=0.1, + output_contract="normalized-rgb", + support_policy="common", + ) + + +def test_loader_process_configuration_requires_explicit_start_method() -> None: + with pytest.raises(ValueError, match="multiprocessing_start_method"): + RunConfiguration( + protocol_id="loader-supply", + decoder_id="pillow", + package_id="a" * 64, + manifest_id="b" * 64, + selection_id="c" * 64, + requested_threads=None, + num_workers=2, + batch_size=1, + prefetch_factor=1, + persistent_workers=True, + multiprocessing_start_method=None, + logical_repeat_factor=1, + warmup_passes=1, + timed_passes_per_run=2, + minimum_timed_seconds=0.1, + output_contract="normalized-rgb", + support_policy="common", + ) + + +def _plan_yaml(descriptor_path: Path, descriptor: dict[str, object]) -> str: + workload = descriptor["workloads"]["fixture"] + return f"""\ +schema_version: "2.0" +experiment_name: fixture-study +seed: 42 +repetitions: 2 +dataset: + descriptor: "{descriptor_path}" + package_id: "{descriptor["package_id"]}" + workload_id: fixture + manifest_id: "{workload["manifest_id"]}" + selection: + method: all + expected_items: 4 + logical_repeat_factor: 1 +matrix: + decoders: + pillow: + threads: [default] + protocols: + decode-memory: {{}} +measurement: + warmup_passes: 1 + timed_passes_per_run: 1 + minimum_timed_seconds: 0.01 + output_contract: normalized-rgb + support_policy: common +execution: + per_run_subprocess: true + run_timeout_seconds: 30 + checkpoint_each_run: true + maximum_memory_fraction: 0.6 +""" diff --git a/tests/test_provenance.py b/tests/test_provenance.py new file mode 100644 index 0000000..8ec6a9a --- /dev/null +++ b/tests/test_provenance.py @@ -0,0 +1,120 @@ +from __future__ import annotations + +import json +from pathlib import Path + +import pytest + +from imread_benchmark.environments import ( + EnvironmentDescriptor, + load_environment_descriptor, + write_environment_descriptor, +) +from imread_benchmark.platforms import ( + PlatformDescriptor, + capture_current_platform, + load_platform_descriptor, + platform_comparison_id, + platform_location, + write_platform_descriptor, +) + + +def test_environment_descriptor_is_content_addressed_and_tamper_checked(tmp_path: Path) -> None: + descriptor = EnvironmentDescriptor.build( + dependency_group="mainstream", + lock_sha256="1" * 64, + project_sha256="2" * 64, + runner_revision="3" * 64, + python={"implementation": "cpython", "version": "3.12.9", "abi": "cpython-312"}, + platform_tags=("macosx_15_0_arm64",), + distributions=(("numpy", "2.3.1"), ("pillow", "11.3.0")), + native_backends={"pillow-jpeg": "libjpeg-turbo 3.1.0"}, + ) + + path = write_environment_descriptor(tmp_path / "environment.json", descriptor) + + assert load_environment_descriptor(path) == descriptor + assert ( + descriptor.environment_id + == EnvironmentDescriptor.build( + dependency_group="mainstream", + lock_sha256="1" * 64, + project_sha256="2" * 64, + runner_revision="3" * 64, + python={"implementation": "cpython", "version": "3.12.9", "abi": "cpython-312"}, + platform_tags=("macosx_15_0_arm64",), + distributions=(("pillow", "11.3.0"), ("numpy", "2.3.1")), + native_backends={"pillow-jpeg": "libjpeg-turbo 3.1.0"}, + ).environment_id + ) + + document = json.loads(path.read_text()) + document["distributions"][0]["version"] = "999" + path.write_text(json.dumps(document)) + with pytest.raises(ValueError, match="environment_id"): + load_environment_descriptor(path) + + +def test_platform_id_uses_stable_identity_but_not_dynamic_runtime(tmp_path: Path) -> None: + identity = { + "architecture": "x86_64", + "cloud_provider": "gcp", + "cpu_model": "Intel Xeon", + "location": "us-central1-a", + "machine_type": "c3-standard-8", + "logical_cpu_count": 8, + } + first = PlatformDescriptor.build(identity=identity, runtime={"kernel": "6.8.0", "free_ram_bytes": 10}) + second = PlatformDescriptor.build(identity=identity, runtime={"kernel": "6.8.1", "free_ram_bytes": 5}) + + assert first.platform_id == second.platform_id + assert first != second + changed = PlatformDescriptor.build( + identity={**identity, "machine_type": "c4-standard-8"}, + runtime=first.runtime, + ) + assert changed.platform_id != first.platform_id + + path = write_platform_descriptor(tmp_path / "platform.json", first) + assert load_platform_descriptor(path) == first + document = json.loads(path.read_text()) + document["identity"]["logical_cpu_count"] = 16 + path.write_text(json.dumps(document)) + with pytest.raises(ValueError, match="platform_id"): + load_platform_descriptor(path) + + +def test_capture_keeps_zone_as_provenance_not_platform_identity() -> None: + first = capture_current_platform( + cloud_provider="gcp", + machine_type="c4d-standard-16", + location="us-central1-a", + ) + second = capture_current_platform( + cloud_provider="gcp", + machine_type="c4d-standard-16", + location="us-central1-b", + ) + + assert first.platform_id == second.platform_id + assert first.provenance == {"location": "us-central1-a"} + assert second.provenance == {"location": "us-central1-b"} + assert "location" not in first.identity + + +def test_comparison_platform_id_normalizes_legacy_zone_identity() -> None: + legacy = PlatformDescriptor.build( + identity={"architecture": "x86_64", "location": "us-central1-a", "machine_type": "c4d-standard-16"}, + runtime={}, + ) + current = PlatformDescriptor.build( + identity={"architecture": "x86_64", "machine_type": "c4d-standard-16"}, + runtime={}, + provenance={"location": "us-central1-b"}, + ) + + assert legacy.platform_id != current.platform_id + assert platform_comparison_id(legacy.to_dict()) == current.platform_id + assert platform_location(legacy.to_dict()) == "us-central1-a" + assert platform_location(current.to_dict()) == "us-central1-b" diff --git a/tests/test_publication_v2.py b/tests/test_publication_v2.py new file mode 100644 index 0000000..62666a9 --- /dev/null +++ b/tests/test_publication_v2.py @@ -0,0 +1,119 @@ +from __future__ import annotations + +import json +from pathlib import Path + +import pytest + +from imread_benchmark.analysis.publication import PublicationError, publish +from imread_benchmark.artifacts import write_run_bundle +from tests.factories import valid_bundle_data + + +def test_publication_is_deterministic_claim_gated_and_traceable_to_raw_bundles(tmp_path: Path) -> None: + artifact_root = tmp_path / "artifacts" + run_keys: list[str] = [] + for position, repetition, decoder_id, elapsed in ( + (0, 0, "pillow", (2.0, 1.0)), + (1, 0, "opencv", (1.0, 0.5)), + (2, 1, "pillow", (1.0, 0.5)), + (3, 1, "opencv", (0.5, 0.25)), + ): + run_key, data = valid_bundle_data( + decoder_id=decoder_id, + block_position=position, + repetition=repetition, + elapsed_seconds=elapsed, + ) + run_keys.append(run_key) + write_run_bundle(root=artifact_root / "runs", run_key=run_key, data=data) + spec = tmp_path / "publication.yaml" + spec.write_text( + """\ +schema_version: "2.0" +claim_scope: decoder-capacity +filters: + config.protocol_id: decode-memory + dataset.workload_id: fixture +statistic: images_per_second +practical_margin_percent: 5 +generator_revision: "1111111111111111111111111111111111111111" +""", + ) + output = tmp_path / "generated" + + publish(artifact_root=artifact_root, spec_path=spec, output_dir=output) + + table = json.loads((output / "results.json").read_text()) + provenance = json.loads((output / "provenance.json").read_text()) + assert [row["run_key"] for row in table["rows"]] == sorted(run_keys) + assert table["rows"][0]["raw_samples"] + assert len(table["groups"]) == 2 + assert all(group["n"] == 2 for group in table["groups"]) + assert all(len(group["raw_run_means"]) == 2 for group in table["groups"]) + assert all(group["sample_std"] is not None for group in table["groups"]) + assert provenance["bundle_keys"] == sorted(run_keys) + assert provenance["claim_scope"] == "decoder-capacity" + assert len(provenance["output_sha256"]) == 64 + publish(artifact_root=artifact_root, spec_path=spec, output_dir=output, check=True) + + (output / "results.json").write_text("{}") + with pytest.raises(PublicationError, match="stale"): + publish(artifact_root=artifact_root, spec_path=spec, output_dir=output, check=True) + + +def test_publication_rejects_training_claim_scope_for_decoder_bundles(tmp_path: Path) -> None: + artifact_root = tmp_path / "artifacts" + run_key, data = valid_bundle_data() + write_run_bundle(root=artifact_root / "runs", run_key=run_key, data=data) + spec = tmp_path / "publication.yaml" + spec.write_text( + """\ +schema_version: "2.0" +claim_scope: training +filters: {} +statistic: images_per_second +practical_margin_percent: 5 +generator_revision: "1111111111111111111111111111111111111111" +""", + ) + + with pytest.raises(ValueError, match="training"): + publish(artifact_root=artifact_root, spec_path=spec, output_dir=tmp_path / "generated") + + +def test_publication_groups_legacy_runs_across_zones_but_retains_zone_provenance(tmp_path: Path) -> None: + artifact_root = tmp_path / "artifacts" + for repetition, zone in ((0, "us-central1-a"), (1, "us-central1-b")): + run_key, data = valid_bundle_data( + repetition=repetition, + block_position=repetition, + platform_identity={ + "architecture": "fixture", + "location": zone, + "logical_cpu_count": 1, + "machine_type": "fixture", + }, + ) + write_run_bundle(root=artifact_root / "runs", run_key=run_key, data=data) + spec = tmp_path / "publication.yaml" + spec.write_text( + """\ +schema_version: "2.0" +claim_scope: decoder-capacity +filters: + config.protocol_id: decode-memory +statistic: images_per_second +practical_margin_percent: 5 +generator_revision: "1111111111111111111111111111111111111111" +""", + ) + + publish(artifact_root=artifact_root, spec_path=spec, output_dir=tmp_path / "generated") + + table = json.loads((tmp_path / "generated" / "results.json").read_text()) + assert len(table["groups"]) == 1 + group = table["groups"][0] + assert group["n"] == 2 + assert len(group["platform_ids"]) == 2 + assert group["platform_locations"] == ["us-central1-a", "us-central1-b"] diff --git a/tests/test_remote_resume.py b/tests/test_remote_resume.py new file mode 100644 index 0000000..b338a75 --- /dev/null +++ b/tests/test_remote_resume.py @@ -0,0 +1,76 @@ +from __future__ import annotations + +import json +from pathlib import Path + +import pytest + +from imread_benchmark.artifacts import ( + RemoteArtifactError, + hydrate_committed_runs, + publish_run_bundle, + pull_committed_run, + validate_run_bundle, + write_run_bundle, +) +from imread_benchmark.datasets.materializer import LocalObjectStore +from tests.factories import valid_bundle_data + + +def _bundle(root: Path) -> tuple[str, Path]: + run_key, data = valid_bundle_data() + return run_key, write_run_bundle( + root=root, + run_key=run_key, + data=data, + ) + + +def test_remote_commit_is_last_and_fresh_machine_resumes_without_rewriting_bundle(tmp_path: Path) -> None: + run_key, source = _bundle(tmp_path / "source-runs") + store = LocalObjectStore(tmp_path / "object-store") + + publish_run_bundle(source, store=store) + marker = tmp_path / "object-store" / "artifacts" / "runs" / run_key / "COMMITTED.json" + assert marker.is_file() + + pulled = pull_committed_run(run_key, store=store, artifact_root=tmp_path / "fresh-machine") + assert pulled is not None + validate_run_bundle(pulled, expected_run_key=run_key) + mtime = (pulled / "COMMITTED.json").stat().st_mtime_ns + assert pull_committed_run(run_key, store=store, artifact_root=tmp_path / "fresh-machine") == pulled + assert (pulled / "COMMITTED.json").stat().st_mtime_ns == mtime + + +def test_incomplete_remote_prefix_is_ignored_but_broken_commit_is_terminal(tmp_path: Path) -> None: + store = LocalObjectStore(tmp_path / "object-store") + incomplete_key = "1" * 64 + incomplete = tmp_path / "object-store" / "artifacts" / "bundles" / ("2" * 64) + incomplete.mkdir(parents=True) + (incomplete / "summary.json").write_text("{}") + assert pull_committed_run(incomplete_key, store=store, artifact_root=tmp_path / "fresh") is None + + run_key, source = _bundle(tmp_path / "source-runs") + publish_run_bundle(source, store=store) + marker = tmp_path / "object-store" / "artifacts" / "runs" / run_key / "COMMITTED.json" + document = json.loads(marker.read_text()) + document["bundle_id"] = "9" * 64 + marker.write_text(json.dumps(document)) + + with pytest.raises(RemoteArtifactError, match="missing"): + pull_committed_run(run_key, store=store, artifact_root=tmp_path / "broken-fresh") + + +def test_downloaded_remote_layout_hydrates_to_valid_local_runs(tmp_path: Path) -> None: + """A raw rsync of remote artifacts can be validated after local hydration.""" + run_key, source = _bundle(tmp_path / "source-runs") + downloaded_root = tmp_path / "downloaded" + publish_run_bundle(source, store=LocalObjectStore(downloaded_root)) + + hydrated = hydrate_committed_runs( + source_artifact_root=downloaded_root / "artifacts", + destination_artifact_root=tmp_path / "hydrated", + ) + + assert [path.name for path in hydrated] == [run_key] + validate_run_bundle(hydrated[0], expected_run_key=run_key) diff --git a/tests/test_statistics.py b/tests/test_statistics.py new file mode 100644 index 0000000..de15674 --- /dev/null +++ b/tests/test_statistics.py @@ -0,0 +1,64 @@ +from __future__ import annotations + +import pytest + +from imread_benchmark.analysis.statistics import summarize_benchmark, summarize_distribution + + +def test_distribution_uses_sample_standard_deviation() -> None: + summary = summarize_distribution((10.0, 20.0, 30.0)) + + assert summary.n == 3 + assert summary.mean == 20.0 + assert summary.median == 20.0 + assert summary.minimum == 10.0 + assert summary.maximum == 30.0 + assert summary.sample_std == 10.0 + assert summary.coefficient_of_variation == 0.5 + + +def test_single_sample_has_no_sample_standard_deviation() -> None: + summary = summarize_distribution((12.5,)) + + assert summary.sample_std is None + assert summary.coefficient_of_variation is None + + +def test_benchmark_summary_keeps_units_and_sample_statistics() -> None: + summary = summarize_benchmark((2.0, 1.0), items_processed=10) + + assert summary == { + "elapsed_seconds": { + "coefficient_of_variation": pytest.approx(0.47140452079103173), + "maximum": 2.0, + "mean": 1.5, + "median": 1.5, + "minimum": 1.0, + "n": 2, + "sample_std": pytest.approx(0.7071067811865476), + }, + "images_per_second": { + "coefficient_of_variation": pytest.approx(0.47140452079103173), + "maximum": 10.0, + "mean": 7.5, + "median": 7.5, + "minimum": 5.0, + "n": 2, + "sample_std": pytest.approx(3.5355339059327378), + }, + "microseconds_per_image": { + "coefficient_of_variation": pytest.approx(0.47140452079103173), + "maximum": 200000.0, + "mean": 150000.0, + "median": 150000.0, + "minimum": 100000.0, + "n": 2, + "sample_std": pytest.approx(70710.67811865476), + }, + } + + +@pytest.mark.parametrize("values", [(), (0.0,), (1.0, -1.0)]) +def test_distribution_rejects_empty_or_non_positive_measurements(values: tuple[float, ...]) -> None: + with pytest.raises(ValueError, match="positive"): + summarize_distribution(values) diff --git a/tests/test_support.py b/tests/test_support.py new file mode 100644 index 0000000..3c81e94 --- /dev/null +++ b/tests/test_support.py @@ -0,0 +1,191 @@ +from __future__ import annotations + +import json +import os +from pathlib import Path + +import numpy as np +import pytest + +from imread_benchmark.datasets.package import build_dataset_package, open_dataset_package +from imread_benchmark.decoders import BaseDecoder +from imread_benchmark.support import ( + SupportAuditContext, + audit_decoder_support, + build_common_support, + build_operational_support, + intersect_supported_items, + load_support_audit, + load_support_set, + write_support_audit, + write_support_set, +) +from imread_benchmark.support.dataloader import audit_decoder_support_in_dataloader + + +class _AcceptDecoder(BaseDecoder): + name = "accept" + package_name = "pytest" + + def decode(self, data: bytes) -> np.ndarray: + return np.zeros((1, 1, 3), dtype=np.uint8) + + +class _RejectFirstDecoder(_AcceptDecoder): + name = "reject-first" + + def __init__(self, rejected: bytes) -> None: + self.rejected = rejected + + def decode(self, data: bytes) -> np.ndarray: + if data == self.rejected: + raise ValueError("unsupported test JPEG") + return super().decode(data) + + +def test_common_support_is_a_pinned_intersection_in_selection_order(tmp_path: Path, jpeg_dir: Path) -> None: + descriptor_path = build_dataset_package( + package_name="fixture-jpegs", + workloads={"fixture": jpeg_dir}, + output_root=tmp_path / "packages", + provenance={"source": "pytest"}, + ) + items = open_dataset_package(descriptor_path).read_workload_items("fixture") + selection_id = "selection-test" + context = SupportAuditContext( + manifest_id="manifest-test", + selection_id=selection_id, + process_context="in-process", + multiprocessing_start_method=None, + requested_threads=None, + environment_id="environment-test", + platform_id="platform-test", + ) + accepted = audit_decoder_support( + _AcceptDecoder(), + items, + context, + ) + rejected = audit_decoder_support( + _RejectFirstDecoder(items[0].data), + items, + context, + ) + + support = build_common_support((accepted, rejected), ordered_selection=tuple(item.item_id for item in items)) + + assert accepted.successful_item_ids == tuple(item.item_id for item in items) + assert rejected.successful_item_ids == tuple(item.item_id for item in items[1:]) + assert rejected.failures[0].item_id == items[0].item_id + assert support.item_ids == tuple(item.item_id for item in items[1:]) + assert support.support_set_id + + +def test_comparison_intersection_spans_main_process_and_dataloader_contexts( + tmp_path: Path, + jpeg_dir: Path, +) -> None: + descriptor_path = build_dataset_package( + package_name="comparison-support-fixture", + workloads={"fixture": jpeg_dir}, + output_root=tmp_path / "packages", + provenance={"source": "pytest"}, + ) + items = open_dataset_package(descriptor_path).read_workload_items("fixture") + base = { + "manifest_id": "manifest-test", + "selection_id": "selection-test", + "requested_threads": None, + "environment_id": "environment-test", + "platform_id": "platform-test", + } + main = audit_decoder_support( + _AcceptDecoder(), + items, + SupportAuditContext(process_context="main-process", multiprocessing_start_method=None, **base), + ) + worker = audit_decoder_support( + _RejectFirstDecoder(items[0].data), + items, + SupportAuditContext(process_context="dataloader", multiprocessing_start_method="spawn", **base), + ) + + item_ids = intersect_supported_items( + (main, worker), + ordered_selection=tuple(item.item_id for item in items), + ) + + assert item_ids == tuple(item.item_id for item in items[1:]) + + +def test_support_artifacts_are_content_addressed_round_trip_and_tamper_checked( + tmp_path: Path, + jpeg_dir: Path, +) -> None: + descriptor_path = build_dataset_package( + package_name="support-store-fixture", + workloads={"fixture": jpeg_dir}, + output_root=tmp_path / "packages", + provenance={"source": "pytest"}, + ) + items = open_dataset_package(descriptor_path).read_workload_items("fixture") + context = SupportAuditContext( + manifest_id="1" * 64, + selection_id="2" * 64, + process_context="main-process", + multiprocessing_start_method=None, + requested_threads=None, + environment_id="3" * 64, + platform_id="4" * 64, + ) + audit = audit_decoder_support(_AcceptDecoder(), items, context) + support_set = build_operational_support(audit) + + audit_path = write_support_audit(tmp_path / "support", audit) + set_path = write_support_set(tmp_path / "support", support_set) + + assert audit_path.name == f"{audit.audit_id}.json" + assert set_path.name == f"{support_set.support_set_id}.json" + assert load_support_audit(audit_path) == audit + assert load_support_set(set_path) == support_set + assert write_support_audit(tmp_path / "support", audit) == audit_path + assert write_support_set(tmp_path / "support", support_set) == set_path + + document = json.loads(set_path.read_text()) + document["item_ids"] = document["item_ids"][1:] + set_path.write_text(json.dumps(document)) + with pytest.raises(ValueError, match="support_set_id"): + load_support_set(set_path) + + +def test_support_audit_runs_decode_inside_a_real_dataloader_worker(tmp_path: Path, jpeg_dir: Path) -> None: + descriptor_path = build_dataset_package( + package_name="support-dataloader-fixture", + workloads={"fixture": jpeg_dir}, + output_root=tmp_path / "packages", + provenance={"source": "pytest"}, + ) + items = open_dataset_package(descriptor_path).read_workload_items("fixture") + context = SupportAuditContext( + manifest_id="1" * 64, + selection_id="2" * 64, + process_context="dataloader", + multiprocessing_start_method="spawn", + requested_threads=None, + environment_id="3" * 64, + platform_id="4" * 64, + ) + + result = audit_decoder_support_in_dataloader( + "pillow", + items, + context, + requested_threads=None, + ) + + assert result.audit.successful_item_ids == tuple(item.item_id for item in items) + assert result.audit.failures == () + assert len(result.worker_handshakes) == 1 + assert result.worker_handshakes[0]["process_id"] != os.getpid() + assert result.worker_handshakes[0]["effective_threads"] == 1 + assert result.worker_handshakes[0]["multiprocessing_start_method"] == "spawn" diff --git a/tests/test_support_worker.py b/tests/test_support_worker.py new file mode 100644 index 0000000..f783af4 --- /dev/null +++ b/tests/test_support_worker.py @@ -0,0 +1,84 @@ +from __future__ import annotations + +import json +import os +import subprocess +import sys +from pathlib import Path + +from imread_benchmark.datasets.package import build_dataset_package, open_dataset_package +from imread_benchmark.support import load_committed_support_audit +from imread_benchmark.support.spec import SupportAuditIdentity, SupportAuditSpec, write_support_audit_spec + + +def test_support_audit_worker_is_a_fresh_resumable_subprocess(tmp_path: Path, jpeg_dir: Path) -> None: + descriptor_path = build_dataset_package( + package_name="support-worker-fixture", + workloads={"fixture": jpeg_dir}, + output_root=tmp_path / "packages", + provenance={"source": "pytest"}, + ) + package = open_dataset_package(descriptor_path) + workload = package.descriptor["workloads"]["fixture"] + package_id = package.descriptor["package_id"] + assert isinstance(workload, dict) + assert isinstance(package_id, str) + manifest_id = workload["manifest_id"] + assert isinstance(manifest_id, str) + item_ids = tuple(item.item_id for item in package.read_workload_items("fixture")) + spec = SupportAuditSpec.build( + identity=SupportAuditIdentity( + decoder_id="pillow", + requested_threads=None, + package_id=package_id, + workload_id="fixture", + manifest_id=manifest_id, + selection_id="2" * 64, + item_ids=item_ids, + process_context="main-process", + multiprocessing_start_method=None, + environment_id="3" * 64, + platform_id="4" * 64, + runner_revision="1" * 40, + ), + package_descriptor=descriptor_path, + ) + spec_path = write_support_audit_spec(tmp_path / "audit-spec.json", spec) + artifact_root = tmp_path / "artifacts" + + first = subprocess.run( # noqa: S603 - launches the package's own support worker with controlled argv + ( + sys.executable, + "-m", + "imread_benchmark.support.worker", + "--audit-spec", + str(spec_path), + "--artifact-root", + str(artifact_root), + ), + check=True, + capture_output=True, + text=True, + ) + first_result = json.loads(first.stdout) + audit = load_committed_support_audit(artifact_root / "support", spec.audit_key) + + assert first_result["process_id"] != os.getpid() + assert first_result["audit_id"] == audit.audit_id + assert audit.successful_item_ids == item_ids + + second = subprocess.run( # noqa: S603 - repeats the same controlled worker to verify resume + ( + sys.executable, + "-m", + "imread_benchmark.support.worker", + "--audit-spec", + str(spec_path), + "--artifact-root", + str(artifact_root), + ), + check=True, + capture_output=True, + text=True, + ) + assert json.loads(second.stdout)["audit_id"] == audit.audit_id diff --git a/tests/test_utils.py b/tests/test_utils.py deleted file mode 100644 index a97b2a3..0000000 --- a/tests/test_utils.py +++ /dev/null @@ -1,32 +0,0 @@ -from __future__ import annotations - -from imread_benchmark.utils import collect_jpeg_paths, get_system_identifier - - -def test_get_system_identifier_is_nonempty(): - ident = get_system_identifier() - assert isinstance(ident, str) - assert len(ident) > 0 - - -def test_get_system_identifier_contains_os(): - ident = get_system_identifier().lower() - assert "darwin" in ident or "linux" in ident or "unknown" in ident - - -def test_collect_jpeg_paths_returns_subset(tmp_path): - (tmp_path / "a.jpg").write_bytes(b"fake") - (tmp_path / "b.jpeg").write_bytes(b"fake") - (tmp_path / "c.png").write_bytes(b"fake") - - paths = collect_jpeg_paths(tmp_path, num_images=10) - assert len(paths) == 2 - assert all(p.suffix.lower() in {".jpg", ".jpeg"} for p in paths) - - -def test_collect_jpeg_paths_respects_limit(tmp_path): - for i in range(5): - (tmp_path / f"{i}.jpg").write_bytes(b"fake") - - paths = collect_jpeg_paths(tmp_path, num_images=3) - assert len(paths) == 3 diff --git a/tests/test_workers.py b/tests/test_workers.py deleted file mode 100644 index 261dddd..0000000 --- a/tests/test_workers.py +++ /dev/null @@ -1,302 +0,0 @@ -""" -Worker-output schema tests. - -`benchmark_single` and `benchmark_dataloader` write JSON files that downstream -plotting / paper tables read by key. A regression that drops or renames a key -silently breaks every downstream consumer. These tests assert the documented -contract: - - - filename pattern (`{lib}_1t_results.json`, `{lib}_default_results.json`, - `{lib}_dataloader_results.json`) - - top-level keys (library, run_tag, requested_threads, effective_threads, ...) - - per-config DataLoader entries written incrementally (one per num_workers) - -DataLoader is unit-tested with `num_workers=0` only — multiprocessing in CI -runners is flaky, and the per-config save logic doesn't depend on workers > 0. -""" - -from __future__ import annotations - -import importlib.util -import json -import subprocess -import sys - -import pytest - -pillow_available = importlib.util.find_spec("PIL") is not None -torch_available = importlib.util.find_spec("torch") is not None - -REQUIRED_SINGLE_KEYS = { - "library", - "mode", - "run_tag", - "requested_threads", - "effective_threads", - "system_info", - "benchmark_results", - "num_images", - "num_runs", -} - -REQUIRED_BENCHMARK_RESULTS_KEYS = { - "images_per_second_mean", - "images_per_second_std", - "images_per_second_p50", - "images_per_second_p90", - "images_per_second_p99", - "us_per_image_mean", - "us_per_image_p50", - "us_per_image_p90", - "us_per_image_p99", - "raw_times_s", - "raw_throughput_ips", - # Skip-on-bad-image schema. Without these the cloud orchestrator can't tell - # "all 50k decoded cleanly" from "49997 decoded, 3 skipped (CMYK)" — and - # losing that distinction in the paper would be a methodological lie. - "num_images_total", - "num_images_decoded", - "num_images_skipped", - "skip_rate", - "skip_indices", - "skip_examples", -} - -REQUIRED_DL_WORKER_KEYS = { - "num_workers", - "num_warmup", - "images_per_second_mean", - "images_per_second_p50", - "us_per_image_mean", - "raw_times_s", -} - - -@pytest.mark.skipif(not pillow_available, reason="pillow not installed") -def test_single_worker_writes_full_schema(tmp_path, jpeg_dir): - """Run benchmark_single via subprocess (matches how cli.py invokes it) and check JSON.""" - out = tmp_path / "results" - proc = subprocess.run( # noqa: S603 - [ - sys.executable, - "-m", - "imread_benchmark.benchmark_single", - "--library", - "pillow", - "--data-dir", - str(jpeg_dir), - "--num-images", - "4", - "--num-runs", - "2", - "--output-dir", - str(out), - "--num-threads", - "1", - ], - check=False, - capture_output=True, - text=True, - ) - assert proc.returncode == 0, f"worker failed: {proc.stderr}" - - files = list(out.rglob("pillow_1t_results.json")) - assert len(files) == 1, f"expected one pillow_1t_results.json, got {files}" - j = json.loads(files[0].read_text()) - - missing = REQUIRED_SINGLE_KEYS - j.keys() - assert not missing, f"missing top-level keys: {missing}" - - assert j["library"] == "pillow" - assert j["run_tag"] == "1t" - assert j["requested_threads"] == 1 - assert j["effective_threads"] >= 1 - assert j["num_images"] == 4 - assert j["num_runs"] == 2 - - br_missing = REQUIRED_BENCHMARK_RESULTS_KEYS - j["benchmark_results"].keys() - assert not br_missing, f"missing benchmark_results keys: {br_missing}" - assert len(j["benchmark_results"]["raw_times_s"]) == 2 - - -@pytest.mark.skipif(not pillow_available, reason="pillow not installed") -def test_single_worker_default_threads_filename(tmp_path, jpeg_dir): - """--num-threads 0 must write `{lib}_default_results.json`, never collide with 1t.""" - out = tmp_path / "results" - proc = subprocess.run( # noqa: S603 - [ - sys.executable, - "-m", - "imread_benchmark.benchmark_single", - "--library", - "pillow", - "--data-dir", - str(jpeg_dir), - "--num-images", - "4", - "--num-runs", - "1", - "--output-dir", - str(out), - "--num-threads", - "0", - ], - check=False, - ) - assert proc.returncode == 0 - files = sorted(p.name for p in out.rglob("*.json")) - assert files == ["pillow_default_results.json"], f"unexpected files: {files}" - - -@pytest.mark.skipif(not (pillow_available and torch_available), reason="dataloader test needs pillow + torch") -def test_dataloader_incremental_save(tmp_path, jpeg_dir): - """JSON must be flushed after every num_workers config, not just at the end.""" - out = tmp_path / "results" - proc = subprocess.run( # noqa: S603 - [ - sys.executable, - "-m", - "imread_benchmark.benchmark_dataloader", - "--library", - "pillow", - "--data-dir", - str(jpeg_dir), - "--num-images", - "4", - "--num-runs", - "1", - "--output-dir", - str(out), - "--workers", - "0", # in-process only — avoids fork/spawn flake in CI - ], - check=False, - capture_output=True, - text=True, - ) - assert proc.returncode == 0, f"worker failed: {proc.stderr}" - - files = list(out.rglob("pillow_dataloader_results.json")) - assert len(files) == 1, f"expected one dataloader json, got {files}" - j = json.loads(files[0].read_text()) - - assert j["library"] == "pillow" - assert j["benchmark_type"] == "dataloader" - assert isinstance(j["worker_results"], list) - assert len(j["worker_results"]) == 1 - - wr = j["worker_results"][0] - missing = REQUIRED_DL_WORKER_KEYS - wr.keys() - assert not missing, f"missing worker_results keys: {missing}" - assert wr["num_workers"] == 0 - assert wr["num_warmup"] >= 1 - - # Skip-rate fields must surface in the dataloader JSON too — Pillow on - # synthetic clean fixtures should hit zero skips. If a future change makes - # the dataloader path silently swallow skips, this catches it. - for k in ( - "num_images_total", - "num_images_decoded", - "num_images_skipped", - "skip_rate", - "skip_indices", - "skip_examples", - ): - assert k in j, f"dataloader json missing key: {k}" - assert j["num_images_skipped"] == 0 - assert j["num_images_decoded"] == j["num_images_total"] - assert j["skip_rate"] == 0.0 - - -@pytest.mark.skipif(not pillow_available, reason="pillow not installed") -def test_cli_writes_run_summary(tmp_path, jpeg_dir): - """ - `imread-benchmark run` must write run_summary.json next to the per-decoder - JSONs, with `exit_status` reflecting whether any decoders failed. - vm_startup.sh's "DONE vs FAILED" decision relies on this — a missing or - malformed summary would silently regress us to "any decoder hiccup nukes - the whole 4-hour run". - """ - # The CLI shells out to venvs/mainstream/bin/python via --skip-setup. If - # that doesn't exist in the dev/CI checkout, the subprocess would fail with - # FileNotFoundError before producing the summary — so the test isn't - # exercising the actual run_summary.json codepath. Skip cleanly instead of - # logging a misleading red. - import pathlib - - if not pathlib.Path("venvs/mainstream/bin/python").exists(): - pytest.skip("venvs/mainstream/bin/python missing — run `imread-benchmark run` once locally first") - - out = tmp_path / "results" - proc = subprocess.run( # noqa: S603 - [ - sys.executable, - "-m", - "imread_benchmark.cli", - "run", - "--data-dir", - str(jpeg_dir), - "--output-dir", - str(out), - "--libs", - "pillow", - "--mode", - "single", - "--num-images", - "4", - "--num-runs", - "1", - "--skip-setup", - ], - check=False, - capture_output=True, - text=True, - ) - # Even on partial success this must exit 0 so vm_startup.sh writes DONE. - assert proc.returncode == 0, f"cli failed: {proc.stderr}" - - summaries = list(out.rglob("run_summary.json")) - assert len(summaries) == 1, f"expected one run_summary.json, got {summaries}" - s = json.loads(summaries[0].read_text()) - - for k in ( - "timestamp_utc", - "system", - "mode", - "num_images", - "libs_requested", - "libs_run", - "failures", - "exit_status", - ): - assert k in s, f"run_summary.json missing key: {k}" - assert s["exit_status"] == "ok" - assert s["failures"] == [] - assert s["libs_run"] == ["pillow"] - - -@pytest.mark.skipif(not pillow_available, reason="pillow not installed") -def test_unknown_library_exits_nonzero(tmp_path, jpeg_dir): - """Bad --library must fail loudly, not write a garbage JSON.""" - out = tmp_path / "results" - proc = subprocess.run( # noqa: S603 - [ - sys.executable, - "-m", - "imread_benchmark.benchmark_single", - "--library", - "definitely_not_a_real_decoder", - "--data-dir", - str(jpeg_dir), - "--num-images", - "4", - "--num-runs", - "1", - "--output-dir", - str(out), - ], - check=False, - capture_output=True, - ) - assert proc.returncode != 0 - assert not list(out.rglob("*.json")) diff --git a/tools/_results.py b/tools/_results.py deleted file mode 100644 index 3985f7a..0000000 --- a/tools/_results.py +++ /dev/null @@ -1,175 +0,0 @@ -""" -Shared loader for imread-benchmark JSON results. - -Used by both `tools/create_plots.py` (charts) and `tools/render_readme.py` -(README tables) so they agree on schema, column names, and library aliasing. -""" - -from __future__ import annotations - -import json -from pathlib import Path - -import pandas as pd - -LIBRARY_ORDER: tuple[str, ...] = ( - "simplejpeg", - "turbojpeg", - "jpeg4py", - "kornia-rs", - "ajpegli", - "opencv", - "imagecodecs", - "pyvips", - "pillow", - "skimage", - "imageio", - "torchvision", - "tensorflow", -) - - -def _alias(library: str) -> str: - return "kornia-rs" if library == "kornia" else library - - -def _extract_throughput(results: dict) -> tuple[float | None, float | None]: - """ - Read mean / std img/s from a benchmark_results dict. - - New format (post-Track 2b): images_per_second_mean / _std as floats. - Legacy format (older runs): images_per_second as "MEAN ± STD" string. - """ - mean = results.get("images_per_second_mean") - std = results.get("images_per_second_std") - if mean is not None: - return float(mean), float(std) if std is not None else None - - legacy = results.get("images_per_second") - if isinstance(legacy, str) and "±" in legacy: - m_str, s_str = legacy.split("±") - try: - return float(m_str.strip()), float(s_str.strip()) - except ValueError: - return None, None - return None, None - - -def load_results(input_dir: Path) -> pd.DataFrame: - """ - Load every output//_t_results.json into a DataFrame. - - Single-thread / fixed-thread results only — dataloader files are skipped - here and handled by load_dataloader_results(). - - Columns: platform, library, run_tag, num_threads, num_images, num_runs, - images_per_second, std_dev, p50, p90, p99, us_per_image, - skip_rate, num_images_skipped, cpu_brand, os_name. - """ - rows: list[dict] = [] - for platform_dir in sorted(p for p in input_dir.iterdir() if p.is_dir()): - platform = platform_dir.name - for result_file in sorted(platform_dir.glob("*_results.json")): - if result_file.name.endswith("_dataloader_results.json"): - continue - with result_file.open() as f: - data = json.load(f) - - results = data.get("benchmark_results", {}) - sysinfo = data.get("system_info", {}) - cpu = sysinfo.get("CPU", {}) if isinstance(sysinfo.get("CPU"), dict) else {} - - mean_ips, std_ips = _extract_throughput(results) - - rows.append( - { - "platform": platform, - "library": _alias(data["library"]), - "run_tag": data.get("run_tag", ""), - "num_threads": data.get("num_threads") or data.get("effective_threads") or 1, - "num_images": data.get("num_images"), - "num_runs": data.get("num_runs"), - "images_per_second": mean_ips, - "std_dev": std_ips, - "p50": results.get("images_per_second_p50"), - "p90": results.get("images_per_second_p90"), - "p99": results.get("images_per_second_p99"), - "us_per_image": results.get("us_per_image_mean"), - "skip_rate": results.get("skip_rate"), - "num_images_skipped": results.get("num_images_skipped"), - "cpu_brand": cpu.get("brand_raw", "Unknown CPU"), - "os_name": sysinfo.get("OS", platform.split("_")[0].title()), - }, - ) - return pd.DataFrame(rows) - - -def load_dataloader_results(input_dir: Path) -> pd.DataFrame: - """ - Load *_dataloader_results.json into a long DataFrame. - - Columns: platform, library, num_workers, images_per_second, std_dev, - cpu_brand, os_name, num_images, num_runs. - """ - rows: list[dict] = [] - for platform_dir in sorted(p for p in input_dir.iterdir() if p.is_dir()): - platform = platform_dir.name - for result_file in sorted(platform_dir.glob("*_dataloader_results.json")): - with result_file.open() as f: - data = json.load(f) - - sysinfo = data.get("system_info", {}) - cpu = sysinfo.get("CPU", {}) if isinstance(sysinfo.get("CPU"), dict) else {} - library = _alias(data["library"]) - - wrs = data.get("worker_results", []) - rows.extend( - { - "platform": platform, - "library": library, - "num_workers": wr.get("num_workers"), - "images_per_second": wr.get("images_per_second_mean"), - "std_dev": wr.get("images_per_second_std"), - "cpu_brand": cpu.get("brand_raw", "Unknown CPU"), - "os_name": sysinfo.get("OS", platform.split("_")[0].title()), - "num_images": data.get("num_images"), - "num_runs": data.get("num_runs"), - } - for wr in wrs - ) - return pd.DataFrame(rows) - - -_KEEP_UPPER = {"AMD", "ARM", "CPU", "EPYC", "GPU", "GHz", "IBM"} - - -def _prettify_cpu(brand: str) -> str: - """Trim noise from a cpuinfo brand string and de-shout ALL CAPS tokens.""" - s = brand.replace("(R)", "").replace("(TM)", "") - s = " ".join(s.split()) - if "@" in s: - s = s.split("@", 1)[0].strip() - out: list[str] = [] - for tok in s.split(" "): - if tok in _KEEP_UPPER or tok.upper() == "CPU": - if tok.upper() == "CPU": - continue - out.append(tok) - elif tok.isalpha() and tok.isupper() and len(tok) > 2: - out.append(tok.title()) - else: - out.append(tok) - return " ".join(out) - - -def short_platform(platform: str, cpu_brand: str | None = None) -> str: - """ - Turn 'linux_AMD-EPYC-9B45' or a recorded CPU brand into a compact label. - - Prefers the recorded `cpu_brand` (e.g. 'AMD EPYC 9B45') when available, - falling back to splitting the directory name. - """ - if cpu_brand and cpu_brand != "Unknown CPU": - return _prettify_cpu(cpu_brand) - _, _, rest = platform.partition("_") - return rest.replace("-", " ") if rest else platform diff --git a/tools/analyze_images.py b/tools/analyze_images.py deleted file mode 100644 index 640823f..0000000 --- a/tools/analyze_images.py +++ /dev/null @@ -1,59 +0,0 @@ -import statistics -from pathlib import Path - -from PIL import Image - - -def analyze_images(folder_path: str | Path, limit: int = 2000) -> None: - """Analyze first N JPEG images in the given folder.""" - folder = Path(folder_path) - file_sizes = [] - resolutions = [] - - # Collect data from first 'limit' images - for img_path in sorted(folder.glob("*.*"))[:limit]: - if img_path.suffix.lower() in {".jpg", ".jpeg"}: - # Get file size in KB - file_sizes.append(img_path.stat().st_size / 1024) - - # Get image resolution - with Image.open(img_path) as img: - resolutions.append(img.size) - - if not file_sizes: - print("No JPEG images found in the folder") - return - - # Analyze file sizes - avg_size = statistics.mean(file_sizes) - min_size = min(file_sizes) - max_size = max(file_sizes) - median_size = statistics.median(file_sizes) - - # Analyze resolutions - widths, heights = zip(*resolutions, strict=False) - min_res = (min(widths), min(heights)) - max_res = (max(widths), max(heights)) - avg_res = (statistics.mean(widths), statistics.mean(heights)) - median_res = (statistics.median(widths), statistics.median(heights)) - - # Print results - print(f"Analysis of first {len(file_sizes)} images:") - print("\nFile Sizes (KB):") - print(f"- Average: {avg_size:.1f}") - print(f"- Median: {median_size:.1f}") - print(f"- Range: {min_size:.1f} - {max_size:.1f}") - print("\nResolutions (pixels):") - print(f"- Average: {avg_res[0]:.0f} x {avg_res[1]:.0f}") - print(f"- Median: {median_res[0]:.0f} x {median_res[1]:.0f}") - print(f"- Range: {min_res[0]}x{min_res[1]} - {max_res[0]}x{max_res[1]}") - - -if __name__ == "__main__": - import sys - - if len(sys.argv) != 2: - print("Usage: python script.py ") - sys.exit(1) - - analyze_images(sys.argv[1]) diff --git a/tools/create_plots.py b/tools/create_plots.py deleted file mode 100644 index 7e2a767..0000000 --- a/tools/create_plots.py +++ /dev/null @@ -1,231 +0,0 @@ -""" -Generate paper-quality plots from imread-benchmark JSON results. - -Reads numeric fields directly (no string parsing) and derives titles from -each result's recorded `system_info`, so adding a new platform is zero-config. - -Run via the CLI: - imread-benchmark plot --input output --output docs/assets/benchmarks - -Or directly (from the repo root): - python -m tools.create_plots --input output --output docs/assets/benchmarks -""" - -from __future__ import annotations - -import argparse -from pathlib import Path - -import matplotlib.pyplot as plt -import pandas as pd -import seaborn as sns - -from tools._results import LIBRARY_ORDER, load_dataloader_results, load_results, short_platform -from tools.paper_assets import ( - CLAIM_FIGURE_BASENAMES, - plot_fig01_protocol_rank_change, - plot_fig02_amd_worker_delta, - plot_fig03_tensorflow_arm_penalty, - plot_fig04_cross_platform_recommendation, - validate_paper_data, -) - -README_SINGLE_PLOT = "single_thread_overview.png" -README_DATALOADER_PLOT = "dataloader_peak_overview.png" - -sns.set_theme(style="whitegrid", font="DejaVu Sans") -sns.set_context("paper", font_scale=1.5) - - -def _title_for_platform(df_platform: pd.DataFrame) -> str: - """Build the figure title from the platform's recorded system_info, no hard-coded map.""" - cpu = df_platform["cpu_brand"].iloc[0] - os_name = df_platform["os_name"].iloc[0] - return f"JPEG Decoding Speed — {os_name} / {cpu}" - - -def plot_platform_performance(df: pd.DataFrame, platform: str, output_path: Path) -> None: - plt.style.use("default") - sns.set_theme(style="whitegrid", font="DejaVu Sans") - - # Single-thread snapshot only (avoids mixing 1-thread and N-thread bars). - pdata = df[(df["platform"] == platform) & (df["num_threads"] == 1)].copy() - if pdata.empty: - return - pdata = pdata.sort_values("images_per_second", ascending=True) - - plt.figure(figsize=(7, 5)) - n_bars = len(pdata) - colors = sns.color_palette("Blues", n_colors=n_bars) - bars = plt.barh(range(len(pdata)), pdata["images_per_second"], height=0.7, color=colors) - - plt.errorbar( - pdata["images_per_second"], - range(len(pdata)), - xerr=pdata["std_dev"], - fmt="none", - color="black", - capsize=4, - alpha=0.5, - linewidth=1.5, - ) - - for i, bar in enumerate(bars): - width = bar.get_width() - text_color = "white" if i > n_bars / 2 else "black" - plt.text( - width / 2, - bar.get_y() + bar.get_height() / 2, - f"{width:.0f}", - ha="center", - va="center", - color=text_color, - fontsize=14, - fontweight="bold", - ) - - plt.title(_title_for_platform(pdata), pad=20, fontsize=14, fontweight="bold") - plt.xlabel("Images per Second (1 thread)", fontsize=14, fontweight="bold") - plt.yticks(range(len(pdata)), pdata["library"], fontsize=14) - - plt.gca().spines["left"].set_linewidth(1.5) - plt.gca().spines["bottom"].set_linewidth(1.5) - plt.grid(True, axis="x", linestyle="--", alpha=0.3, linewidth=1.5) - plt.tight_layout(pad=1.2) - plt.savefig(output_path, dpi=600, bbox_inches="tight") - plt.close() - - -def _platform_labels(df: pd.DataFrame) -> dict[str, str]: - labels: dict[str, str] = {} - for platform in sorted(df["platform"].unique()): - cpu = df[df["platform"] == platform]["cpu_brand"].iloc[0] - labels[platform] = short_platform(platform, cpu) - return labels - - -def _ordered_libraries(df: pd.DataFrame) -> list[str]: - seen = set(df["library"].unique()) - known = [lib for lib in LIBRARY_ORDER if lib in seen] - unknown = sorted(seen - set(known)) - return [*known, *unknown] - - -def _leaderboard_matrix(df: pd.DataFrame) -> tuple[pd.DataFrame, dict[str, str]]: - labels = _platform_labels(df) - pivot = df.pivot_table( - index="library", - columns="platform", - values="images_per_second", - aggfunc="max", - ) - pivot = pivot.reindex(index=_ordered_libraries(df), columns=labels.keys()) - pivot = pivot.rename(columns=labels) - return pivot, labels - - -def _annotate_values(values: pd.DataFrame, relative: pd.DataFrame) -> pd.DataFrame: - annotations = values.copy().astype("object") - for row in values.index: - for col in values.columns: - value = values.loc[row, col] - pct = relative.loc[row, col] - annotations.loc[row, col] = "" if pd.isna(value) else f"{value:,.0f}\n{pct:.0f}%" - return annotations - - -def _plot_overview_heatmap(values: pd.DataFrame, title: str, output_path: Path) -> None: - relative = values.div(values.max(axis=0), axis=1) * 100 - annotations = _annotate_values(values, relative) - - height = max(5.0, 0.42 * len(values.index) + 1.8) - width = max(8.0, 1.45 * len(values.columns) + 3.2) - plt.figure(figsize=(width, height)) - ax = sns.heatmap( - relative, - vmin=0, - vmax=100, - cmap="YlGnBu", - linewidths=0.5, - linecolor="white", - annot=annotations, - fmt="", - cbar_kws={"label": "% of fastest on that platform"}, - ) - ax.set_title(title, pad=18, fontsize=16, fontweight="bold") - ax.set_xlabel("") - ax.set_ylabel("") - ax.tick_params(axis="x", rotation=25) - ax.tick_params(axis="y", rotation=0) - plt.tight_layout(pad=1.2) - plt.savefig(output_path, dpi=300, bbox_inches="tight") - plt.close() - - -def plot_single_thread_overview(df: pd.DataFrame, output_path: Path) -> None: - single = df[df["run_tag"] == "1t"].copy() - if single.empty: - return - values, _ = _leaderboard_matrix(single) - _plot_overview_heatmap(values, "Single-thread JPEG decode throughput", output_path) - - -def plot_dataloader_overview(df: pd.DataFrame, output_path: Path) -> None: - if df.empty: - return - peak_idx = df.groupby(["platform", "library"])["images_per_second"].idxmax() - peaks = df.loc[peak_idx].copy() - values, _ = _leaderboard_matrix(peaks) - _plot_overview_heatmap(values, "Peak PyTorch DataLoader throughput", output_path) - - -def plot_claim_first_readme_figures(single: pd.DataFrame, dl: pd.DataFrame, output_dir: Path) -> bool: - """Write the paper/README claim-first figure set when the full paper matrix is present.""" - try: - validate_paper_data(single, dl) - except ValueError as exc: - print(f"paper claim-first figures skipped: {exc}") - return False - - sns.set_theme(style="whitegrid", context="paper", font="DejaVu Sans", font_scale=1.1) - plot_fig01_protocol_rank_change(single, dl, output_dir, ("png",)) - plot_fig02_amd_worker_delta(dl, output_dir, ("png",)) - plot_fig03_tensorflow_arm_penalty(single, output_dir, ("png",)) - plot_fig04_cross_platform_recommendation(dl, output_dir, ("png",)) - for basename in CLAIM_FIGURE_BASENAMES: - print(f"wrote {output_dir / (basename + '.png')}") - return True - - -def main() -> None: - parser = argparse.ArgumentParser(description="Plot imread-benchmark single-thread results.") - parser.add_argument("--input", type=Path, default=Path("output")) - parser.add_argument("--output", type=Path, default=Path("docs/assets/benchmarks")) - args = parser.parse_args() - - df = load_results(args.input) - if df.empty: - print(f"No results found under {args.input}//*_results.json") - return - - args.output.mkdir(parents=True, exist_ok=True) - - ddf = load_dataloader_results(args.input) - wrote_claim_figures = plot_claim_first_readme_figures(df, ddf, args.output) - if not wrote_claim_figures: - single_overview = args.output / README_SINGLE_PLOT - plot_single_thread_overview(df, single_overview) - print(f"wrote {single_overview}") - - dataloader_overview = args.output / README_DATALOADER_PLOT - plot_dataloader_overview(ddf, dataloader_overview) - print(f"wrote {dataloader_overview}") - - for platform in sorted(df["platform"].unique()): - out_file = args.output / f"performance_{platform}.png" - plot_platform_performance(df, platform, out_file) - print(f"wrote {out_file}") - - -if __name__ == "__main__": - main() diff --git a/tools/paper_assets.py b/tools/paper_assets.py deleted file mode 100644 index 3ec8c6f..0000000 --- a/tools/paper_assets.py +++ /dev/null @@ -1,995 +0,0 @@ -""" -Generate NeurIPS paper tables and figures from imread-benchmark JSON under output/. - -Uses tools._results loaders so numbers agree with README / tools/create_plots. - -Usage (from repo root, with plot extras): - - uv run --extra plot python -m tools.paper_assets --all - uv run --extra plot python -m tools.paper_assets --tables - uv run --extra plot python -m tools.paper_assets --figures -""" - -from __future__ import annotations - -import argparse -import json -from pathlib import Path -from typing import TYPE_CHECKING, Any - -import numpy as np -import pandas as pd - -from tools._results import LIBRARY_ORDER, load_dataloader_results, load_results, short_platform - -if TYPE_CHECKING: - from types import ModuleType - -plt: Any = None -sns: Any = None -Patch: Any = None - - -def _require_plotting() -> tuple[ModuleType, ModuleType, type]: - global Patch, plt, sns - if plt is not None and sns is not None and Patch is not None: - return plt, sns, Patch - try: - import matplotlib as mpl - import matplotlib.pyplot as plt - import seaborn as sns - from matplotlib.patches import Patch - except ImportError as exc: - msg = "Figure generation requires matplotlib and seaborn. Install with `uv sync --extra plot`." - raise RuntimeError(msg) from exc - mpl.rcParams["pdf.fonttype"] = 42 - mpl.rcParams["ps.fonttype"] = 42 - return plt, sns, Patch - - -# --------------------------------------------------------------------------- -# Paper scope: five GCP -standard-16 platforms (directory names under output/) -# --------------------------------------------------------------------------- - -PAPER_PLATFORMS: tuple[str, ...] = ( - "linux_INTEL(R)-XEON(R)-PLATINUM-8581C-CPU-@-2.30GHz", - "linux_AMD-EPYC-9B14", - "linux_AMD-EPYC-9B45", - "linux_Neoverse-V2", - "linux_Neoverse-N1", -) - -PLATFORM_MACHINE: dict[str, str] = { - "linux_INTEL(R)-XEON(R)-PLATINUM-8581C-CPU-@-2.30GHz": "c4-standard-16", - "linux_AMD-EPYC-9B14": "c3d-standard-16", - "linux_AMD-EPYC-9B45": "c4d-standard-16", - "linux_Neoverse-V2": "c4a-standard-16", - "linux_Neoverse-N1": "t2a-standard-16", -} - -PLATFORM_MICROARCH: dict[str, str] = { - "linux_INTEL(R)-XEON(R)-PLATINUM-8581C-CPU-@-2.30GHz": "Intel Emerald Rapids", - "linux_AMD-EPYC-9B14": "AMD Zen 4 (Genoa)", - "linux_AMD-EPYC-9B45": "AMD Zen 5 (Turin)", - "linux_Neoverse-V2": "ARM Neoverse V2 (Google Axion)", - "linux_Neoverse-N1": "ARM Neoverse N1 (Ampere Altra)", -} - -PLATFORM_SMT: dict[str, str] = { - "linux_INTEL(R)-XEON(R)-PLATINUM-8581C-CPU-@-2.30GHz": "yes (SMT2)", - "linux_AMD-EPYC-9B14": "yes (SMT2)", - "linux_AMD-EPYC-9B45": "yes (SMT2)", - "linux_Neoverse-V2": "no", - "linux_Neoverse-N1": "no", -} - -ZEN4_PLATFORM = "linux_AMD-EPYC-9B14" -ZEN5_PLATFORM = "linux_AMD-EPYC-9B45" - -WORKERS_ORDER = (0, 2, 4, 8) -EXPECTED_SINGLE_DECODERS = set(LIBRARY_ORDER) -EXPECTED_DATALOADER_DECODERS = set(LIBRARY_ORDER) - {"pyvips", "tensorflow"} -EXPECTED_SKIP_DECODERS = {"ajpegli", "jpeg4py", "kornia-rs", "turbojpeg"} -ROBUSTNESS_DECODERS = ("ajpegli", "jpeg4py", "kornia-rs", "turbojpeg", "pyvips", "tensorflow") - -FIG_DPI = 300 -CLAIM_FIGURE_BASENAMES: tuple[str, ...] = ( - "fig01_protocol_rank_change", - "fig02_amd_worker_delta", - "fig03_tensorflow_arm_penalty", - "fig04_decoder_recommendation_summary", -) -RANK_CHANGE_PLATFORMS: tuple[str, ...] = ( - "linux_INTEL(R)-XEON(R)-PLATINUM-8581C-CPU-@-2.30GHz", - ZEN4_PLATFORM, - "linux_Neoverse-V2", -) -RANK_CHANGE_HIGHLIGHTS: dict[str, str] = { - "linux_INTEL(R)-XEON(R)-PLATINUM-8581C-CPU-@-2.30GHz": "imageio", - ZEN4_PLATFORM: "torchvision", - "linux_Neoverse-V2": "imageio", -} - - -def _paper_scope(df: pd.DataFrame) -> pd.DataFrame: - return df[df["platform"].isin(PAPER_PLATFORMS)].copy() - - -def _platform_labels(cpu_by_platform: dict[str, str]) -> dict[str, str]: - return {p: short_platform(p, cpu_by_platform.get(p)) for p in PAPER_PLATFORMS} - - -def _cpu_by_platform(df: pd.DataFrame) -> dict[str, str]: - out: dict[str, str] = {} - for p in PAPER_PLATFORMS: - sub = df[df["platform"] == p] - if not sub.empty: - out[p] = str(sub["cpu_brand"].iloc[0]) - return out - - -def _ordered_libs_present(df: pd.DataFrame) -> list[str]: - seen = set(df["library"].unique()) - return [lib for lib in LIBRARY_ORDER if lib in seen] - - -def _md_row(cells: list[str]) -> str: - return "| " + " | ".join(cells) + " |" - - -def _md_table(headers: list[str], rows: list[list[str]]) -> str: - lines = [_md_row(headers), _md_row(["---"] * len(headers))] - lines.extend(_md_row(r) for r in rows) - return "\n".join(lines) + "\n" - - -def _fmt_mean_std(mean: float | None, std: float | None) -> str: - if pd.isna(mean): - return "—" - if pd.isna(std): - return f"{mean:.0f}" - return f"{mean:.0f} ± {std:.0f}" - - -def _fmt_ips(v: float | None) -> str: - if pd.isna(v): - return "—" - return f"{v:.0f}" - - -def _latex_escape(s: str) -> str: - replacements = { - "\\": r"\textbackslash{}", - "&": r"\&", - "%": r"\%", - "$": r"\$", - "#": r"\#", - "_": r"\_", - "{": r"\{", - "}": r"\}", - "~": r"\textasciitilde{}", - "^": r"\textasciicircum{}", - } - return "".join(replacements.get(ch, ch) for ch in s) - - -def _validate_platform_set(label: str, actual: set[str]) -> None: - expected_platforms = set(PAPER_PLATFORMS) - if actual != expected_platforms: - missing = sorted(expected_platforms - actual) - extra = sorted(actual - expected_platforms) - raise ValueError(f"{label} platform mismatch: missing={missing}, extra={extra}") - - -def _validate_single_decoders(single: pd.DataFrame) -> None: - for plat in PAPER_PLATFORMS: - libs = set(single[single["platform"] == plat]["library"].unique()) - if libs != EXPECTED_SINGLE_DECODERS: - missing = sorted(EXPECTED_SINGLE_DECODERS - libs) - extra = sorted(libs - EXPECTED_SINGLE_DECODERS) - raise ValueError(f"{plat} single-thread decoder mismatch: missing={missing}, extra={extra}") - - -def _validate_dataloader_decoders(dl: pd.DataFrame) -> None: - expected_workers = set(WORKERS_ORDER) - for plat in PAPER_PLATFORMS: - dl_sub = dl[dl["platform"] == plat] - dl_libs = set(dl_sub["library"].unique()) - if dl_libs != EXPECTED_DATALOADER_DECODERS: - missing = sorted(EXPECTED_DATALOADER_DECODERS - dl_libs) - extra = sorted(dl_libs - EXPECTED_DATALOADER_DECODERS) - raise ValueError(f"{plat} DataLoader decoder mismatch: missing={missing}, extra={extra}") - for lib in EXPECTED_DATALOADER_DECODERS: - workers = {int(w) for w in dl_sub[dl_sub["library"] == lib]["num_workers"].unique()} - if workers != expected_workers: - raise ValueError(f"{plat}/{lib} worker mismatch: got={sorted(workers)}") - - -def _validate_skip_decoders(single: pd.DataFrame) -> None: - skipped = single[single["num_images_skipped"].fillna(0) > 0] - skipped_libs = set(skipped["library"].unique()) - if skipped_libs != EXPECTED_SKIP_DECODERS: - raise ValueError( - "unexpected single-thread skip decoders: " - f"got={sorted(skipped_libs)}, expected={sorted(EXPECTED_SKIP_DECODERS)}", - ) - for lib in EXPECTED_SKIP_DECODERS: - counts = skipped[skipped["library"] == lib]["num_images_skipped"].astype(int).tolist() - if counts != [1] * len(PAPER_PLATFORMS): - raise ValueError(f"{lib} skip counts must be 1 on every paper platform, got={counts}") - - -def validate_paper_data(df_1t: pd.DataFrame, dl: pd.DataFrame) -> dict[str, int]: - """Validate the result matrix assumed by the paper narrative.""" - single = _paper_scope(df_1t) - single = single[single["run_tag"] == "1t"] - dl = _paper_scope(dl) - - _validate_platform_set("single-thread", set(single["platform"].unique())) - _validate_platform_set("DataLoader", set(dl["platform"].unique())) - _validate_single_decoders(single) - _validate_dataloader_decoders(dl) - _validate_skip_decoders(single) - - return { - "platforms": len(PAPER_PLATFORMS), - "single_thread_decoders": len(EXPECTED_SINGLE_DECODERS), - "dataloader_decoders": len(EXPECTED_DATALOADER_DECODERS), - "single_thread_rows": len(single), - "dataloader_worker_rows": len(dl), - } - - -def generate_hardware_table(df_1t: pd.DataFrame, dest: Path) -> None: - cpu_map = _cpu_by_platform(df_1t) - plat_labels = _platform_labels(cpu_map) - headers = [ - "Output dir", - "GCP machine type", - "CPU (recorded)", - "Microarchitecture (paper label)", - "SMT", - "Short label", - ] - rows: list[list[str]] = [ - [ - f"`{plat}/`", - PLATFORM_MACHINE[plat], - cpu_map.get(plat, "—"), - PLATFORM_MICROARCH[plat], - PLATFORM_SMT[plat], - plat_labels[plat], - ] - for plat in PAPER_PLATFORMS - ] - dest.write_text( - "# Table 1 — Hardware / platform matrix\n\n" - "_Generated by `python -m tools.paper_assets --tables`._\n\n" + _md_table(headers, rows), - encoding="utf-8", - ) - - -def generate_single_thread_table(df_1t: pd.DataFrame, dest: Path) -> None: - df = _paper_scope(df_1t) - df = df[df["run_tag"] == "1t"] - - libs = _ordered_libs_present(df) - plat_labels = _platform_labels(_cpu_by_platform(df)) - headers = ["Decoder"] + [plat_labels[p] for p in PAPER_PLATFORMS] - rows: list[list[str]] = [] - for lib in libs: - row = [f"`{lib}`"] - for plat in PAPER_PLATFORMS: - sub = df[(df["platform"] == plat) & (df["library"] == lib)] - if sub.empty: - row.append("—") - else: - row.append( - _fmt_mean_std( - sub["images_per_second"].iloc[0], - sub["std_dev"].iloc[0], - ), - ) - rows.append(row) - dest.write_text( - "# Table 2 — Single-thread throughput (images/s, mean ± std)\n\n" - "_From `*_1t_results.json` (memory decode, one thread)._\n\n" + _md_table(headers, rows), - encoding="utf-8", - ) - - -def _peak_dataloader_rows(dl: pd.DataFrame) -> pd.DataFrame: - dl = dl[dl["num_workers"].isin(WORKERS_ORDER)] - rows: list[dict] = [] - for plat in PAPER_PLATFORMS: - libs = dl[dl["platform"] == plat]["library"].unique() - for lib in libs: - sub = dl[(dl["platform"] == plat) & (dl["library"] == lib)] - if sub.empty: - continue - idx = sub["images_per_second"].idxmax() - peak = sub.loc[idx] - rows.append( - { - "platform": plat, - "library": lib, - "peak_ips": float(peak["images_per_second"]), - "peak_workers": int(peak["num_workers"]), - }, - ) - return pd.DataFrame(rows) - - -def generate_peak_dataloader_table(dl: pd.DataFrame, dest: Path) -> None: - dl = _paper_scope(dl) - peak = _peak_dataloader_rows(dl) - plat_labels = _platform_labels(_cpu_by_platform(dl)) - libs = _ordered_libs_present(dl) - headers = ["Decoder"] + [plat_labels[p] for p in PAPER_PLATFORMS] - rows: list[list[str]] = [] - for lib in libs: - row = [f"`{lib}`"] - for plat in PAPER_PLATFORMS: - sub = peak[(peak["platform"] == plat) & (peak["library"] == lib)] - if sub.empty: - row.append("—") - else: - w = int(sub["peak_workers"].iloc[0]) - ips = float(sub["peak_ips"].iloc[0]) - row.append(f"{ips:.0f} (w={w})") - rows.append(row) - dest.write_text( - "# Table 3 — Peak DataLoader throughput (images/s) and best worker count\n\n" - "_Max over workers ∈ {0, 2, 4, 8}. Decoders without DataLoader JSON omitted._\n\n" + _md_table(headers, rows), - encoding="utf-8", - ) - - -def _ips_at_workers(dl: pd.DataFrame, platform: str, lib: str, workers: int) -> float | None: - sub = dl[(dl["platform"] == platform) & (dl["library"] == lib) & (dl["num_workers"] == workers)] - if sub.empty: - return None - return float(sub["images_per_second"].iloc[0]) - - -def generate_amd_w4_w8_table(dl: pd.DataFrame, dest: Path) -> None: - dl = _paper_scope(dl) - libs = [lib for lib in _ordered_libs_present(dl) if lib != "pyvips"] - headers = ["Decoder", "Zen4 w=4", "Zen4 w=8", "Zen5 w=4", "Zen5 w=8"] - rows: list[list[str]] = [] - for lib in libs: - z4_4 = _ips_at_workers(dl, ZEN4_PLATFORM, lib, 4) - z4_8 = _ips_at_workers(dl, ZEN4_PLATFORM, lib, 8) - z5_4 = _ips_at_workers(dl, ZEN5_PLATFORM, lib, 4) - z5_8 = _ips_at_workers(dl, ZEN5_PLATFORM, lib, 8) - if z4_4 is None and z4_8 is None and z5_4 is None and z5_8 is None: - continue - rows.append( - [ - f"`{lib}`", - _fmt_ips(z4_4), - _fmt_ips(z4_8), - _fmt_ips(z5_4), - _fmt_ips(z5_8), - ], - ) - dest.write_text( - "# Table 4 — AMD Zen 4 vs Zen 5: DataLoader at w=4 and w=8 (images/s)\n\n" - "_Zen 4 = `linux_AMD-EPYC-9B14`, Zen 5 = `linux_AMD-EPYC-9B45`._\n\n" + _md_table(headers, rows), - encoding="utf-8", - ) - - -def generate_recommendation_table(dl: pd.DataFrame, dest: Path) -> None: - norm = _peak_pct_of_platform_winner(dl) - summary = ( - norm.groupby("library", as_index=False)["pct_of_winner"] - .agg(mean="mean", min="min", max="max") - .set_index("library") - ) - recommendation_order = ["torchvision", "simplejpeg", "opencv"] - headers = [ - "Decoder", - "Mean % of winner", - "Min %", - "Max %", - "Skipped JPEGs", - "DataLoader platforms", - ] - rows: list[list[str]] = [] - for lib in recommendation_order: - if lib not in summary.index: - continue - vals = summary.loc[lib] - platform_count = int(norm[norm["library"] == lib]["platform"].nunique()) - skip_text = "1 / 50,000" if lib in EXPECTED_SKIP_DECODERS else "0 / 50,000" - rows.append( - [ - f"`{lib}`", - f"{vals['mean']:.1f}%", - f"{vals['min']:.1f}%", - f"{vals['max']:.1f}%", - skip_text, - f"{platform_count} / {len(PAPER_PLATFORMS)}", - ], - ) - note = ( - "\n_Peak DataLoader throughput normalized to the platform-local winner. " - "This curated table highlights operationally relevant choices used in the paper discussion._\n" - ) - dest.write_text( - "# Table 6 — Operational DataLoader recommendation summary\n\n" + note + "\n" + _md_table(headers, rows), - encoding="utf-8", - ) - - -def _format_platform_choice(row: pd.Series) -> str: - lib = _latex_escape(str(row["library"])) - ips = float(row["peak_ips"]) - workers = int(row["peak_workers"]) - return rf"\texttt{{{lib}}}: {ips:.0f} img/s ($w={workers}$)" - - -def _platform_recommendation_choices(dl: pd.DataFrame) -> list[tuple[str, pd.DataFrame]]: - dl = _paper_scope(dl) - peak = _peak_dataloader_rows(dl) - zero_skip_loader_decoders = EXPECTED_DATALOADER_DECODERS - EXPECTED_SKIP_DECODERS - - choices_by_platform: list[tuple[str, pd.DataFrame]] = [] - for plat in PAPER_PLATFORMS: - sub = peak[(peak["platform"] == plat) & (peak["library"].isin(zero_skip_loader_decoders))] - sub = sub.sort_values(["peak_ips", "library"], ascending=[False, True]) - if len(sub) < 3: - raise ValueError(f"{plat} has fewer than three zero-skip DataLoader choices") - choices_by_platform.append((plat, sub.head(3).reset_index(drop=True))) - return choices_by_platform - - -def _platform_recommendation_rows(dl: pd.DataFrame) -> list[list[str]]: - plat_labels = _platform_labels(_cpu_by_platform(_paper_scope(dl))) - - rows: list[list[str]] = [] - for plat, choices_df in _platform_recommendation_choices(dl): - choices = [_format_platform_choice(choices_df.iloc[i]) for i in range(3)] - rows.append( - [ - _latex_escape(plat_labels[plat]), - *choices, - rf"GCP \texttt{{{_latex_escape(PLATFORM_MACHINE[plat])}}}", - ], - ) - return rows - - -def _latex_table_row(row: list[str]) -> str: - return " " + " & ".join(row) + r" \\" - - -def generate_platform_recommendation_table(dl: pd.DataFrame, dest: Path) -> None: - headers = [ - "Platform", - "First zero-skip choice", - "Second zero-skip choice", - "Third zero-skip choice", - "Note", - ] - rows = _platform_recommendation_rows(dl) - body = "\n".join(_latex_table_row(row) for row in rows) - header = " & ".join(headers) - caption = ( - r" \caption{Per-platform zero-skip DataLoader starting points. Values are measured peak PyTorch " - r"\texttt{DataLoader} throughput on the paper matrix; use them as initial guidance, not as a " - r"universal recommendation.}" - ) - note = ( - r" {\footnotesize Strict native JPEG decoders/wrappers may be fast but skipped one ImageNet JPEG; " - r"PyVips and TensorFlow are not PyTorch \texttt{DataLoader} choices in this harness.\par}" - ) - dest.write_text( - "\n".join( - [ - r"\begin{table}[ht]", - r" \centering", - r" \scriptsize", - r" \setlength{\tabcolsep}{2pt}", - r" \renewcommand{\arraystretch}{1.08}", - caption, - r" \label{tab:platform-starting-points}", - r" \begin{tabularx}{\linewidth}{@{}YYYYY@{}}", - r" \toprule", - f" {header} " + r"\\", - r" \midrule", - body, - r" \bottomrule", - r" \end{tabularx}", - r" \vspace{2pt}", - note, - r"\end{table}", - "", - ], - ), - encoding="utf-8", - ) - - -def _raw_library_name(library: str) -> str: - return "kornia" if library == "kornia-rs" else library - - -def _first_skip_example(input_dir: Path, library: str) -> str: - raw = _raw_library_name(library) - for plat in PAPER_PLATFORMS: - for suffix in ("1t_results", "dataloader_results"): - path = input_dir / plat / f"{raw}_{suffix}.json" - if not path.exists(): - continue - data = json.loads(path.read_text(encoding="utf-8")) - if suffix == "1t_results": - examples = data.get("benchmark_results", {}).get("skip_examples", []) - else: - examples = data.get("skip_examples", []) - if examples: - return str(examples[0]).replace("|", "\\|") - return "—" - - -def _skip_summary(df_1t: pd.DataFrame, library: str) -> str: - single = _paper_scope(df_1t) - single = single[(single["run_tag"] == "1t") & (single["library"] == library)] - counts = single["num_images_skipped"].fillna(0).astype(int).tolist() - platform_count = len(PAPER_PLATFORMS) - if not counts or max(counts) == 0: - return f"0 / 50,000 on all {platform_count} platforms" - if counts == [1] * platform_count: - return f"1 / 50,000 on all {platform_count} platforms" - return ", ".join(str(c) for c in counts) - - -def _dataloader_eligibility(input_dir: Path, library: str) -> str: - raw = _raw_library_name(library) - present = [(input_dir / plat / f"{raw}_dataloader_results.json").exists() for plat in PAPER_PLATFORMS] - if all(present): - return "Yes" - if not any(present) and library == "pyvips": - return "No: disabled for forked PyTorch workers" - if not any(present) and library == "tensorflow": - return "No: TensorFlow stack not run inside PyTorch DataLoader" - return f"Partial: {sum(present)} / {len(PAPER_PLATFORMS)} platforms" - - -def generate_robustness_table(input_dir: Path, df_1t: pd.DataFrame, dest: Path) -> None: - headers = ["Decoder", "DataLoader eligibility", "Skipped images", "Example failure", "Interpretation"] - interpretations = { - "ajpegli": "Fast path, but rejects the same uncommon ImageNet image.", - "jpeg4py": "Fast path, but needs an explicit CMYK fallback policy.", - "kornia-rs": "Fast path, but rejects the same uncommon ImageNet image.", - "turbojpeg": "Fast path, but needs an explicit CMYK fallback policy.", - "pyvips": "Single-thread numbers only; no loader-scale recommendation in this harness.", - "tensorflow": "Single-thread portability warning; re-benchmark exact build and pipeline.", - } - rows = [ - [ - f"`{lib}`", - _dataloader_eligibility(input_dir, lib), - _skip_summary(df_1t, lib), - _first_skip_example(input_dir, lib), - interpretations[lib], - ] - for lib in ROBUSTNESS_DECODERS - ] - dest.write_text( - "# Table 5 — Robustness and DataLoader eligibility\n\n" - "_Generated from skip fields in `*_1t_results.json` and `*_dataloader_results.json`._\n\n" - + _md_table(headers, rows), - encoding="utf-8", - ) - - -def _lib_colors(libs: list[str]) -> dict[str, tuple]: - _require_plotting() - pal = sns.color_palette("tab20", n_colors=max(20, len(libs))) - return {lib: pal[i % len(pal)] for i, lib in enumerate(libs)} - - -def _rank_frame(single: pd.DataFrame, dl: pd.DataFrame, platform: str) -> pd.DataFrame: - libs = sorted(EXPECTED_DATALOADER_DECODERS) - single_sub = single[ - (single["platform"] == platform) & (single["run_tag"] == "1t") & (single["library"].isin(libs)) - ].copy() - peaks = _peak_dataloader_rows(dl) - peak_sub = peaks[(peaks["platform"] == platform) & (peaks["library"].isin(libs))].copy() - single_sub["single_rank"] = single_sub["images_per_second"].rank(method="min", ascending=False).astype(int) - peak_sub["dataloader_rank"] = peak_sub["peak_ips"].rank(method="min", ascending=False).astype(int) - ranks = single_sub[["library", "single_rank"]].merge( - peak_sub[["library", "dataloader_rank"]], - on="library", - how="inner", - ) - ranks["rank_delta"] = ranks["single_rank"] - ranks["dataloader_rank"] - return ranks.sort_values(["rank_delta", "library"], ascending=[True, True]) - - -def plot_fig01_protocol_rank_change( - single: pd.DataFrame, - dl: pd.DataFrame, - out_dir: Path, - formats: tuple[str, ...], -) -> None: - _require_plotting() - single = _paper_scope(single) - dl = _paper_scope(dl) - plat_labels = _platform_labels(_cpu_by_platform(single)) - fig, axes = plt.subplots(1, 3, figsize=(11.8, 5.4), sharey=False, constrained_layout=True) - - for ax, plat in zip(axes, RANK_CHANGE_PLATFORMS, strict=True): - ranks = _rank_frame(single, dl, plat) - highlight = RANK_CHANGE_HIGHLIGHTS[plat] - y = np.arange(len(ranks)) - colors = [] - for row in ranks.itertuples(index=False): - if row.library == highlight: - colors.append("#1f77b4") - elif row.rank_delta > 0: - colors.append("#2ca02c") - elif row.rank_delta < 0: - colors.append("#e15759") - else: - colors.append("0.68") - - ax.barh(y, ranks["rank_delta"], color=colors, alpha=0.9) - ax.axvline(0, color="0.25", linewidth=1.1) - for i, row in enumerate(ranks.itertuples(index=False)): - if row.rank_delta == 0: - continue - ha = "left" if row.rank_delta > 0 else "right" - offset = 0.18 if row.rank_delta > 0 else -0.18 - ax.text( - row.rank_delta + offset, - i, - f"{row.single_rank}->{row.dataloader_rank}", - ha=ha, - va="center", - fontsize=7.5, - fontweight="bold" if row.library == highlight else "normal", - color="#1f77b4" if row.library == highlight else "0.2", - ) - limit = max(float(ranks["rank_delta"].abs().max()), len(ranks) - 1) + 0.5 - tick_limit = int(limit - 0.5) - ax.set_title(plat_labels[plat], fontsize=10, fontweight="bold") - ax.set_xlim(-limit, limit) - ax.set_xticks([-tick_limit, -tick_limit // 2, 0, tick_limit // 2, tick_limit]) - ax.set_yticks(y) - ax.set_yticklabels(ranks["library"]) - ax.invert_yaxis() - ax.grid(True, axis="x", alpha=0.22) - ax.set_xlabel("Rank change") - - axes[0].set_ylabel("Decoder") - fig.suptitle("Protocol changes the supported decoder recommendation", fontsize=13, fontweight="bold") - fig.supxlabel( - "single-thread rank - peak DataLoader rank (positive = moves up under DataLoader)", - fontsize=10, - ) - - base = out_dir / CLAIM_FIGURE_BASENAMES[0] - for fmt in formats: - fig.savefig(base.with_suffix(f".{fmt}"), dpi=FIG_DPI, bbox_inches="tight") - plt.close(fig) - - -def plot_fig02_amd_worker_delta(dl: pd.DataFrame, out_dir: Path, formats: tuple[str, ...]) -> None: - _require_plotting() - dl = _paper_scope(dl) - libs = [lib for lib in _ordered_libs_present(dl) if lib not in {"pyvips", "tensorflow"}] - delta_by_platform: dict[str, list[float]] = {} - for plat in (ZEN4_PLATFORM, ZEN5_PLATFORM): - deltas = [] - for lib in libs: - w4 = _ips_at_workers(dl, plat, lib, 4) - w8 = _ips_at_workers(dl, plat, lib, 8) - deltas.append(np.nan if w4 is None or w4 == 0 or w8 is None else 100.0 * (w8 / w4 - 1.0)) - delta_by_platform[plat] = deltas - finite_deltas = [d for deltas in delta_by_platform.values() for d in deltas if not pd.isna(d)] - x_min = min(-18.0, min(finite_deltas) - 5.0) - x_max = max(32.0, max(finite_deltas) + 8.0) - - fig, axes = plt.subplots(1, 2, figsize=(11, 5.6), sharey=True, constrained_layout=True) - labels = {ZEN4_PLATFORM: "AMD Zen 4", ZEN5_PLATFORM: "AMD Zen 5"} - y = np.arange(len(libs)) - - for ax, plat in zip(axes, (ZEN4_PLATFORM, ZEN5_PLATFORM), strict=True): - deltas = delta_by_platform[plat] - colors = [] - for delta in deltas: - if pd.isna(delta): - colors.append("0.68") - else: - colors.append("#2ca02c" if delta >= 0 else "#d62728") - ax.barh(y, deltas, color=colors, alpha=0.88) - ax.axvline(0, color="0.25", linewidth=1.2) - for i, delta in enumerate(deltas): - if pd.isna(delta): - continue - ha = "left" if delta >= 0 else "right" - offset = 0.6 if delta >= 0 else -0.6 - ax.text(delta + offset, i, f"{delta:+.0f}%", va="center", ha=ha, fontsize=8) - ax.set_title(labels[plat], fontsize=10, fontweight="bold") - ax.set_xlabel("Throughput change from w=4 to w=8") - ax.grid(True, axis="x", alpha=0.25) - ax.set_xlim(x_min, x_max) - - axes[0].set_yticks(y) - axes[0].set_yticklabels(libs) - axes[0].invert_yaxis() - fig.suptitle("Worker-count scaling differs between AMD generations", fontsize=13, fontweight="bold") - - base = out_dir / CLAIM_FIGURE_BASENAMES[1] - for fmt in formats: - fig.savefig(base.with_suffix(f".{fmt}"), dpi=FIG_DPI, bbox_inches="tight") - plt.close(fig) - - -def _peak_pct_of_platform_winner(dl: pd.DataFrame) -> pd.DataFrame: - dl = _paper_scope(dl) - peak = _peak_dataloader_rows(dl) - rows: list[dict] = [] - for plat in PAPER_PLATFORMS: - sub = peak[peak["platform"] == plat] - if sub.empty: - continue - winner_ips = float(sub["peak_ips"].max()) - if pd.isna(winner_ips) or winner_ips <= 0: - continue - rows.extend( - [ - { - "platform": plat, - "library": row["library"], - "peak_ips": float(row["peak_ips"]), - "peak_workers": int(row["peak_workers"]), - "pct_of_winner": 100.0 * float(row["peak_ips"]) / winner_ips, - } - for row in sub.to_dict("records") - ], - ) - return pd.DataFrame(rows) - - -def plot_fig03_tensorflow_arm_penalty(single: pd.DataFrame, out_dir: Path, formats: tuple[str, ...]) -> None: - _require_plotting() - single = _paper_scope(single) - single = single[single["run_tag"] == "1t"].copy() - plat_labels = _platform_labels(_cpu_by_platform(single)) - rows: list[dict] = [] - for plat in PAPER_PLATFORMS: - sub = single[single["platform"] == plat] - tf = sub[sub["library"] == "tensorflow"] - if sub.empty or tf.empty: - continue - winner = float(sub["images_per_second"].max()) - tf_ips = float(tf["images_per_second"].iloc[0]) - rows.append( - { - "platform": plat, - "label": plat_labels[plat], - "pct": 100.0 * tf_ips / winner, - "ips": tf_ips, - }, - ) - data = pd.DataFrame(rows) - - fig, ax = plt.subplots(figsize=(8.4, 4.3), constrained_layout=True) - colors = ["#d62728" if "Neoverse" in row.label else "#4c78a8" for row in data.itertuples(index=False)] - bars = ax.bar(data["label"], data["pct"], color=colors, alpha=0.9) - ax.axhline(100, color="0.35", linewidth=1.0, linestyle="--") - for bar, row in zip(bars, data.itertuples(index=False), strict=True): - ax.text( - bar.get_x() + bar.get_width() / 2, - bar.get_height() + 1.2, - f"{row.pct:.0f}%\n{row.ips:.0f} img/s", - ha="center", - va="bottom", - fontsize=8, - ) - ax.set_ylim(0, 110) - ax.set_ylabel("TensorFlow single-thread throughput\n(% of platform winner)") - ax.set_xlabel("") - ax.tick_params(axis="x", rotation=20) - ax.set_title("TensorFlow JPEG decode shows a large ARM penalty", fontweight="bold") - ax.grid(True, axis="y", alpha=0.25) - base = out_dir / CLAIM_FIGURE_BASENAMES[2] - for fmt in formats: - fig.savefig(base.with_suffix(f".{fmt}"), dpi=FIG_DPI, bbox_inches="tight") - plt.close(fig) - - -def plot_fig04_cross_platform_recommendation(dl: pd.DataFrame, out_dir: Path, formats: tuple[str, ...]) -> None: - _require_plotting() - norm = _peak_pct_of_platform_winner(dl) - libs = _ordered_libs_present(norm) - summary = ( - norm.groupby("library", as_index=False)["pct_of_winner"] - .agg(mean="mean", min="min", max="max") - .set_index("library") - .reindex(libs) - .dropna() - .sort_values("mean", ascending=True) - ) - platform_points = norm[norm["library"].isin(summary.index)] - - fig, ax = plt.subplots(figsize=(9.2, 5.6), constrained_layout=True) - y = np.arange(len(summary)) - x_min = 70.0 - zero_skip_color = "#2b8cbe" - skip_color = "#f2a541" - for i, lib in enumerate(summary.index): - has_skip = lib in EXPECTED_SKIP_DECODERS - ax.barh( - i, - summary.loc[lib, "mean"] - x_min, - left=x_min, - height=0.58, - color=skip_color if has_skip else zero_skip_color, - alpha=0.74, - hatch="//" if has_skip else None, - edgecolor="white", - linewidth=0.7, - zorder=2, - ) - ax.errorbar( - summary["mean"], - y, - xerr=[summary["mean"] - summary["min"], summary["max"] - summary["mean"]], - fmt="none", - ecolor="0.25", - elinewidth=1.2, - capsize=3, - zorder=3, - ) - for i, lib in enumerate(summary.index): - pts = platform_points[platform_points["library"] == lib]["pct_of_winner"] - has_skip = lib in EXPECTED_SKIP_DECODERS - ax.scatter( - pts, - np.full(len(pts), i), - s=22, - color="white", - edgecolor=skip_color if has_skip else zero_skip_color, - linewidth=0.8, - alpha=0.85, - zorder=4, - ) - - ax.axvline(90, color="0.35", linestyle="--", linewidth=1.0, zorder=1) - ax.text(90.25, len(summary) - 0.25, "90% practical floor", fontsize=7, color="0.35", va="top") - ax.set_yticks(y) - ax.set_yticklabels(summary.index) - ax.set_xlim(x_min, 103) - ax.set_xlabel("Peak DataLoader throughput (% of platform winner)") - ax.set_title("DataLoader speed and observed JPEG robustness", fontweight="bold") - ax.grid(True, axis="x", alpha=0.3) - ax.spines[["top", "right"]].set_visible(False) - ax.legend( - handles=[ - Patch(facecolor=zero_skip_color, edgecolor="white", label="zero observed skips"), - Patch(facecolor=skip_color, edgecolor="white", hatch="//", label="one skipped JPEG"), - ], - frameon=False, - loc="lower right", - fontsize=8, - ) - base = out_dir / CLAIM_FIGURE_BASENAMES[3] - for fmt in formats: - fig.savefig(base.with_suffix(f".{fmt}"), dpi=FIG_DPI, bbox_inches="tight") - plt.close(fig) - - -def generate_tables(input_dir: Path, paper_dir: Path) -> None: - df_1t = load_results(input_dir) - dl = load_dataloader_results(input_dir) - validate_paper_data(df_1t, dl) - gen = paper_dir / "generated" - gen.mkdir(parents=True, exist_ok=True) - generate_hardware_table(df_1t, gen / "table01_hardware.md") - generate_single_thread_table(df_1t, gen / "table02_single_thread.md") - generate_peak_dataloader_table(dl, gen / "table03_peak_dataloader.md") - generate_amd_w4_w8_table(dl, gen / "table04_amd_w4_w8.md") - generate_robustness_table(input_dir, df_1t, gen / "table05_robustness.md") - generate_recommendation_table(dl, gen / "table06_recommendation_tier.md") - generate_platform_recommendation_table(dl, gen / "table07_platform_recommendations.tex") - (gen / "README.md").write_text( - "# Generated paper tables\n\n" - "- `table01_hardware.md`\n" - "- `table02_single_thread.md`\n" - "- `table03_peak_dataloader.md`\n" - "- `table04_amd_w4_w8.md`\n" - "- `table05_robustness.md`\n" - "- `table06_recommendation_tier.md`\n" - "- `table07_platform_recommendations.tex`\n\n" - "Regenerate from repo root:\n\n" - "```bash\n" - "uv run --extra plot python -m tools.paper_assets --tables\n" - "```\n", - encoding="utf-8", - ) - - -def generate_figures(input_dir: Path, paper_dir: Path, formats: tuple[str, ...]) -> None: - _require_plotting() - sns.set_theme(style="whitegrid", context="paper", font_scale=1.1) - df_1t = load_results(input_dir) - dl = load_dataloader_results(input_dir) - validate_paper_data(df_1t, dl) - fig_dir = paper_dir / "figures" - fig_dir.mkdir(parents=True, exist_ok=True) - plot_fig01_protocol_rank_change(df_1t, dl, fig_dir, formats) - plot_fig02_amd_worker_delta(dl, fig_dir, formats) - plot_fig03_tensorflow_arm_penalty(df_1t, fig_dir, formats) - plot_fig04_cross_platform_recommendation(dl, fig_dir, formats) - - -def _parse_formats(s: str) -> tuple[str, ...]: - parts = tuple(p.strip().lower() for p in s.split(",") if p.strip()) - return parts or ("png",) - - -def main() -> None: - parser = argparse.ArgumentParser(description="Generate paper tables and figures from benchmark JSON.") - parser.add_argument( - "--repo-root", - type=Path, - default=Path.cwd(), - help="Repository root (default: cwd).", - ) - parser.add_argument( - "--input", - type=Path, - default=Path("output"), - help="Results directory relative to repo root (default: output).", - ) - parser.add_argument( - "--paper-dir", - type=Path, - default=Path("_internal/papers"), - help="Paper directory relative to repo root (default: _internal/papers).", - ) - parser.add_argument("--tables", action="store_true", help="Write Markdown tables under paper-dir/generated/.") - parser.add_argument("--figures", action="store_true", help="Write figures under paper-dir/figures/.") - parser.add_argument("--all", action="store_true", help="Tables + figures.") - parser.add_argument("--check", action="store_true", help="Validate the paper result matrix without writing files.") - parser.add_argument( - "--format", - type=str, - default="png,pdf", - help="Figure formats, comma-separated (default: png,pdf).", - ) - args = parser.parse_args() - root = args.repo_root.resolve() - input_dir = (root / args.input).resolve() - paper_dir = (root / args.paper_dir).resolve() - fmt = _parse_formats(args.format) - - if args.check: - summary = validate_paper_data(load_results(input_dir), load_dataloader_results(input_dir)) - print( - "paper data ok: " - f"{summary['platforms']} platforms, " - f"{summary['single_thread_decoders']} single-thread decoders, " - f"{summary['dataloader_decoders']} DataLoader decoders, " - f"{summary['dataloader_worker_rows']} DataLoader worker rows", - ) - return - - do_tables = args.tables or args.all - do_figures = args.figures or args.all - if not do_tables and not do_figures: - parser.error("Specify --tables, --figures, or --all") - - if do_tables: - generate_tables(input_dir, paper_dir) - if do_figures: - generate_figures(input_dir, paper_dir, fmt) - - -if __name__ == "__main__": - main() diff --git a/tools/render_readme.py b/tools/render_readme.py deleted file mode 100644 index 552cbc0..0000000 --- a/tools/render_readme.py +++ /dev/null @@ -1,220 +0,0 @@ -""" -Regenerate the data tables in README.md from output//*.json. - -Three blocks are rewritten in place between sentinel HTML comments — anything -outside the markers is left alone. - - - ... auto-generated table ... - - -Run via the CLI: - imread-benchmark render-readme --input output --readme README.md - -Or directly (from the repo root): - python -m tools.render_readme --input output --readme README.md [--check] -""" - -from __future__ import annotations - -import argparse -import json -import re -import sys -from pathlib import Path - -import pandas as pd - -from tools._results import ( - LIBRARY_ORDER, - load_dataloader_results, - load_results, - short_platform, -) - -MISSING = "—" - - -def _platform_columns(df: pd.DataFrame) -> list[tuple[str, str]]: - """Return ordered (platform_dirname, short_label) tuples.""" - cols: list[tuple[str, str]] = [] - for platform in sorted(df["platform"].unique()): - cpu = df[df["platform"] == platform]["cpu_brand"].iloc[0] - cols.append((platform, short_platform(platform, cpu))) - return cols - - -def _markdown_table(headers: list[str], rows: list[list[str]], align: list[str] | None = None) -> str: - """Build a GitHub-flavored markdown table.""" - if align is None: - align = ["left"] + ["right"] * (len(headers) - 1) - sep = [] - for a in align: - if a == "right": - sep.append("---:") - elif a == "center": - sep.append(":---:") - else: - sep.append(":---") - out = ["| " + " | ".join(headers) + " |", "| " + " | ".join(sep) + " |"] - out.extend("| " + " | ".join(row) + " |" for row in rows) - return "\n".join(out) - - -def _fmt_ips(value: float | None) -> str: - if value is None or pd.isna(value): - return MISSING - return f"{value:,.0f}" - - -def render_single_thread_table(df: pd.DataFrame) -> str: - """Build the rows = library, cols = platform single-thread leaderboard.""" - df1t = df[df["run_tag"] == "1t"] - if df1t.empty: - return "_No single-thread results found._" - - cols = _platform_columns(df1t) - pivot = df1t.pivot_table( - index="library", - columns="platform", - values="images_per_second", - aggfunc="max", - ) - - libraries = [lib for lib in LIBRARY_ORDER if lib in pivot.index] - headers = ["Library", *(short for _, short in cols)] - rows: list[list[str]] = [] - best_per_col = {p: pivot[p].max() for p, _ in cols if p in pivot.columns} - - for lib in libraries: - row = [f"`{lib}`"] - for platform, _ in cols: - value = pivot.get(platform, pd.Series()).get(lib) if platform in pivot.columns else None - cell = _fmt_ips(value) - if value is not None and not pd.isna(value) and value == best_per_col.get(platform): - cell = f"**{cell}**" - row.append(cell) - rows.append(row) - - return _markdown_table(headers, rows) - - -def render_dataloader_table(ddf: pd.DataFrame) -> str: - """Peak DataLoader throughput per (library, platform) — value @ best worker count.""" - if ddf.empty: - return "_No DataLoader results found._" - - idx = ddf.groupby(["platform", "library"])["images_per_second"].idxmax() - peaks = ddf.loc[idx] - cols = _platform_columns(ddf) - - headers = ["Library", *(short for _, short in cols)] - rows: list[list[str]] = [] - best_per_col: dict[str, float] = {} - for platform, _ in cols: - sub = peaks[peaks["platform"] == platform] - if not sub.empty: - best_per_col[platform] = sub["images_per_second"].max() - - libraries = [lib for lib in LIBRARY_ORDER if lib in peaks["library"].unique()] - for lib in libraries: - row = [f"`{lib}`"] - for platform, _ in cols: - cell_df = peaks[(peaks["platform"] == platform) & (peaks["library"] == lib)] - if cell_df.empty: - row.append(MISSING) - continue - ips = cell_df["images_per_second"].iloc[0] - workers = int(cell_df["num_workers"].iloc[0]) - cell = f"{_fmt_ips(ips)} @ {workers}w" - if ips == best_per_col.get(platform): - cell = f"**{cell}**" - row.append(cell) - rows.append(row) - - return _markdown_table(headers, rows) - - -def render_metadata(input_dir: Path, df: pd.DataFrame) -> str: - """One-liner: platform count, dataset size, run count, latest run timestamp.""" - summaries: list[dict] = [] - for platform_dir in sorted(p for p in input_dir.iterdir() if p.is_dir()): - summary_file = platform_dir / "run_summary.json" - if summary_file.exists(): - with summary_file.open() as f: - summaries.append(json.load(f)) - - n_platforms = df["platform"].nunique() - num_images = int(df["num_images"].dropna().max()) if not df["num_images"].dropna().empty else None - num_runs = int(df["num_runs"].dropna().max()) if not df["num_runs"].dropna().empty else None - - timestamps = [s["timestamp_utc"] for s in summaries if s.get("timestamp_utc")] - latest = max(timestamps) if timestamps else None - latest_date = latest.split("T")[0] if latest else "unknown" - - parts = [f"**{n_platforms} platforms**"] - if num_images is not None: - parts.append(f"{num_images:,} images") - if num_runs is not None: - parts.append(f"{num_runs} runs each") - parts.append(f"latest run {latest_date}") - return "_" + " · ".join(parts) + "_" - - -def update_readme(readme_path: Path, blocks: dict[str, str]) -> str: - """Rewrite each `...` block.""" - text = readme_path.read_text() - for key, body in blocks.items(): - pattern = re.compile( - rf"()(.*?)()", - re.DOTALL, - ) - if not pattern.search(text): - raise SystemExit( - f"Marker pair for '{key}' not found in {readme_path}. " - f"Add `` and ``.", - ) - text = pattern.sub(rf"\1\n\n{body}\n\n\3", text) - return text - - -def main() -> int: - parser = argparse.ArgumentParser(description="Regenerate README.md benchmark tables.") - parser.add_argument("--input", type=Path, default=Path("output")) - parser.add_argument("--readme", type=Path, default=Path("README.md")) - parser.add_argument( - "--check", - action="store_true", - help="Exit 1 if README would change (CI / pre-commit guard).", - ) - args = parser.parse_args() - - df = load_results(args.input) - if df.empty: - print(f"No results found under {args.input}//*_results.json", file=sys.stderr) - return 1 - ddf = load_dataloader_results(args.input) - - blocks = { - "single_thread": render_single_thread_table(df), - "dataloader": render_dataloader_table(ddf), - "metadata": render_metadata(args.input, df), - } - new_text = update_readme(args.readme, blocks) - old_text = args.readme.read_text() - - if new_text == old_text: - print(f"{args.readme} already up to date.") - return 0 - - if args.check: - print(f"{args.readme} is stale. Run: python tools/render_readme.py", file=sys.stderr) - return 1 - - args.readme.write_text(new_text) - print(f"updated {args.readme}") - return 0 - - -if __name__ == "__main__": - raise SystemExit(main()) diff --git a/uv.lock b/uv.lock index c0eb332..4a187c6 100644 --- a/uv.lock +++ b/uv.lock @@ -1,32 +1,36 @@ version = 1 -revision = 1 +revision = 3 requires-python = ">=3.11" resolution-markers = [ - "python_full_version >= '3.14' and sys_platform == 'win32'", - "python_full_version >= '3.14' and sys_platform == 'emscripten'", - "python_full_version >= '3.14' and sys_platform != 'darwin' and sys_platform != 'emscripten' and sys_platform != 'win32'", - "python_full_version >= '3.14' and sys_platform == 'darwin'", + "python_full_version >= '3.15' and sys_platform == 'win32'", + "python_full_version == '3.14.*' and sys_platform == 'win32'", + "python_full_version >= '3.15' and sys_platform == 'emscripten'", + "python_full_version == '3.14.*' and sys_platform == 'emscripten'", + "python_full_version >= '3.15' and sys_platform != 'darwin' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.14.*' and sys_platform != 'darwin' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version >= '3.15' and sys_platform == 'darwin'", + "python_full_version == '3.14.*' and sys_platform == 'darwin'", "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.13.*' and sys_platform == 'emscripten'", "python_full_version == '3.12.*' and sys_platform == 'emscripten'", - "python_full_version < '3.12' and sys_platform == 'emscripten'", "python_full_version == '3.13.*' and sys_platform != 'darwin' and sys_platform != 'emscripten' and sys_platform != 'win32'", "python_full_version == '3.12.*' and sys_platform != 'darwin' and sys_platform != 'emscripten' and sys_platform != 'win32'", - "python_full_version < '3.12' and sys_platform != 'darwin' and sys_platform != 'emscripten' and sys_platform != 'win32'", "python_full_version == '3.13.*' and sys_platform == 'darwin'", "python_full_version == '3.12.*' and sys_platform == 'darwin'", + "python_full_version < '3.12' and sys_platform == 'win32'", + "python_full_version < '3.12' and sys_platform == 'emscripten'", + "python_full_version < '3.12' and sys_platform != 'darwin' and sys_platform != 'emscripten' and sys_platform != 'win32'", "python_full_version < '3.12' and sys_platform == 'darwin'", ] [[package]] name = "absl-py" -version = "2.4.0" +version = "2.5.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/64/c7/8de93764ad66968d19329a7e0c147a2bb3c7054c554d4a119111b8f9440f/absl_py-2.4.0.tar.gz", hash = "sha256:8c6af82722b35cf71e0f4d1d47dcaebfff286e27110a99fc359349b247dfb5d4", size = 116543 } +sdist = { url = "https://files.pythonhosted.org/packages/d0/4f/d79676ab82f2e42fc3611618139f13a9c4c31d0cff4b486982047679a802/absl_py-2.5.0.tar.gz", hash = "sha256:0c996f25c0490700fadabe6351630f6111534fa0ae252cc6d2014ea3b141135f", size = 118119, upload-time = "2026-07-03T10:57:48.157Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/18/a6/907a406bb7d359e6a63f99c313846d9eec4f7e6f7437809e03aa00fa3074/absl_py-2.4.0-py3-none-any.whl", hash = "sha256:88476fd881ca8aab94ffa78b7b6c632a782ab3ba1cd19c9bd423abc4fb4cd28d", size = 135750 }, + { url = "https://files.pythonhosted.org/packages/58/0a/a10b45aab35b175aded078a462dc8d0c698f5b13946e7cb0869097b78bb6/absl_py-2.5.0-py3-none-any.whl", hash = "sha256:0f17b89f2a4eaaedc4f28c622998aa690564b3012a396a4ffad0821007fe03ba", size = 137410, upload-time = "2026-07-03T10:57:46.735Z" }, ] [[package]] @@ -34,34 +38,76 @@ name = "ajpegli" version = "1.0.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "numpy" }, + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, + { name = "numpy", version = "2.5.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/30/d9/9e1ebfed347fa500fe027a19e112f5a252ecf935cfccd6ded0a7e601fbad/ajpegli-1.0.0.tar.gz", hash = "sha256:58e52b7a51f3bf1631d574869c0123c8d525edbd2893cfa44e26759cd784713d", size = 97407485 } +sdist = { url = "https://files.pythonhosted.org/packages/30/d9/9e1ebfed347fa500fe027a19e112f5a252ecf935cfccd6ded0a7e601fbad/ajpegli-1.0.0.tar.gz", hash = "sha256:58e52b7a51f3bf1631d574869c0123c8d525edbd2893cfa44e26759cd784713d", size = 97407485, upload-time = "2026-05-19T21:30:04.3Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a5/72/39ada8422379778f8d64444b2ffe3f552deebd328b3370391535d7301e36/ajpegli-1.0.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:95341aab9701da44561aab86b938ad7cb9a214b3eaed41893ab52c159f7aaa83", size = 273768 }, - { url = "https://files.pythonhosted.org/packages/ab/57/1cec79331aeec49a67ea656c78059886cc0a367c514c66bc9195e9e90d40/ajpegli-1.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d8b8f3f21baa47fb539701ffbad50287a4e54c5d7658096bf43f321f0050feae", size = 195503 }, - { url = "https://files.pythonhosted.org/packages/8d/09/5836b713f16dc1ba90c3b5d09a895d3eaf8c2f0a8b962de5a7697504625e/ajpegli-1.0.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7e6e034a181494d149328c02c07c3e246dac09d6717b45e7d56f9f763e9a8791", size = 285550 }, - { url = "https://files.pythonhosted.org/packages/2a/90/49e973b99957da3a56b2e485d4b6d68fd9f906f6c47ef42026fd11ed897b/ajpegli-1.0.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9638b916e236900e099528e30cda109cfb26e3a6a58b29734fd9791b6f965fe0", size = 342508 }, - { url = "https://files.pythonhosted.org/packages/70/10/5bf1eeaf54466f18765518e3da804471cf4488d916fd242c5298397c3bea/ajpegli-1.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:a014c356e2c0bb1684e148d876aecce7909655873d527f0b7531799903b5f6a7", size = 257437 }, - { url = "https://files.pythonhosted.org/packages/b4/06/a0e3c74972771db6212001c06ab86430bd721c19ecd4b5e6c2b4fd55d1e8/ajpegli-1.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:669e1d0e991f6597e816026cf79a1f7c4dc6825f5494d0ed1e5b803255d26df2", size = 277841 }, - { url = "https://files.pythonhosted.org/packages/d6/4d/a16c18aa5e893be388d15fa6994c5ef2d18ac645d8955d6d973b855560e1/ajpegli-1.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3bb23026e4098ad3089a0fc5a15da39816c5e4b8cc18e2601c5177bc7ac7a3ef", size = 197174 }, - { url = "https://files.pythonhosted.org/packages/cd/e9/994546434e06f82f72d98bf6902ade62241f18c455f2303c2c5a77569cca/ajpegli-1.0.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bc6fc8ae251517fac4a0996194bb65fa884079b23ed8cafb2a928d3fdcb3f333", size = 287717 }, - { url = "https://files.pythonhosted.org/packages/8b/7a/9c001b02ba80c4de629bc63038929ad898dafe05eb4df5dc7a17d8896b64/ajpegli-1.0.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:66edffabdaf5e84034793db4bbcb4f462aca332244e2a92c6878621481fce245", size = 344521 }, - { url = "https://files.pythonhosted.org/packages/0b/19/97b491e3c51a39648775393a55196d129153abfa5f62105302b0cef0afe8/ajpegli-1.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:3b9b7b7b3ce9fd042da068d06ba2fb08c64595b2e0aba4420dcf0f49ece12bcd", size = 259641 }, - { url = "https://files.pythonhosted.org/packages/61/9a/86e0cd693cf77290a587dafd2fc7942eb9384eecf5e196b6c3c9f36f3e83/ajpegli-1.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:7681ed14c9cc89a844de21c4feb824f504fca5dad08c89a264429dd35aea4c41", size = 277902 }, - { url = "https://files.pythonhosted.org/packages/d9/22/f124f98e63da211e0b38dc9d155f8ddd84ffc2c64ddff2b5115456dbd80b/ajpegli-1.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d51799aa8786128738e864eb7c8770714864eb7840a6061cf6e90e9461f371d3", size = 197259 }, - { url = "https://files.pythonhosted.org/packages/77/21/b4eb5a4b9a147829a8923d69f8dcd9c15dea972f025276773bae0d26f454/ajpegli-1.0.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1435aea7cb962eddd6e73206b7b3e49b2e7aa1a0c5daadcb9c8d42cecbf30df1", size = 287732 }, - { url = "https://files.pythonhosted.org/packages/f2/53/1fa8bb7ce2a01e554bf905ae4fd2bd1ad99d4de33211e01c0401f91ce0a3/ajpegli-1.0.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:797f2deeaf71d43d4aab64eab2cdef93126d3cfdc7fadfdc16c78e01af5cba05", size = 344707 }, - { url = "https://files.pythonhosted.org/packages/d6/ac/a825b22dcc8e7278773b1dc7c3d8aa0ddb3d04d3bb891176dd464becf4f6/ajpegli-1.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:d78157fb08e5718eb96d5438e28ca90a047d0fc4b0b32d6ca6b0b10a45c13811", size = 259717 }, + { url = "https://files.pythonhosted.org/packages/a5/72/39ada8422379778f8d64444b2ffe3f552deebd328b3370391535d7301e36/ajpegli-1.0.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:95341aab9701da44561aab86b938ad7cb9a214b3eaed41893ab52c159f7aaa83", size = 273768, upload-time = "2026-05-19T21:29:30.765Z" }, + { url = "https://files.pythonhosted.org/packages/ab/57/1cec79331aeec49a67ea656c78059886cc0a367c514c66bc9195e9e90d40/ajpegli-1.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d8b8f3f21baa47fb539701ffbad50287a4e54c5d7658096bf43f321f0050feae", size = 195503, upload-time = "2026-05-19T21:29:32.565Z" }, + { url = "https://files.pythonhosted.org/packages/8d/09/5836b713f16dc1ba90c3b5d09a895d3eaf8c2f0a8b962de5a7697504625e/ajpegli-1.0.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7e6e034a181494d149328c02c07c3e246dac09d6717b45e7d56f9f763e9a8791", size = 285550, upload-time = "2026-05-19T21:29:34.009Z" }, + { url = "https://files.pythonhosted.org/packages/2a/90/49e973b99957da3a56b2e485d4b6d68fd9f906f6c47ef42026fd11ed897b/ajpegli-1.0.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9638b916e236900e099528e30cda109cfb26e3a6a58b29734fd9791b6f965fe0", size = 342508, upload-time = "2026-05-19T21:29:35.477Z" }, + { url = "https://files.pythonhosted.org/packages/70/10/5bf1eeaf54466f18765518e3da804471cf4488d916fd242c5298397c3bea/ajpegli-1.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:a014c356e2c0bb1684e148d876aecce7909655873d527f0b7531799903b5f6a7", size = 257437, upload-time = "2026-05-19T21:29:36.909Z" }, + { url = "https://files.pythonhosted.org/packages/b4/06/a0e3c74972771db6212001c06ab86430bd721c19ecd4b5e6c2b4fd55d1e8/ajpegli-1.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:669e1d0e991f6597e816026cf79a1f7c4dc6825f5494d0ed1e5b803255d26df2", size = 277841, upload-time = "2026-05-19T21:29:38.844Z" }, + { url = "https://files.pythonhosted.org/packages/d6/4d/a16c18aa5e893be388d15fa6994c5ef2d18ac645d8955d6d973b855560e1/ajpegli-1.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3bb23026e4098ad3089a0fc5a15da39816c5e4b8cc18e2601c5177bc7ac7a3ef", size = 197174, upload-time = "2026-05-19T21:29:40.588Z" }, + { url = "https://files.pythonhosted.org/packages/cd/e9/994546434e06f82f72d98bf6902ade62241f18c455f2303c2c5a77569cca/ajpegli-1.0.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bc6fc8ae251517fac4a0996194bb65fa884079b23ed8cafb2a928d3fdcb3f333", size = 287717, upload-time = "2026-05-19T21:29:42.034Z" }, + { url = "https://files.pythonhosted.org/packages/8b/7a/9c001b02ba80c4de629bc63038929ad898dafe05eb4df5dc7a17d8896b64/ajpegli-1.0.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:66edffabdaf5e84034793db4bbcb4f462aca332244e2a92c6878621481fce245", size = 344521, upload-time = "2026-05-19T21:29:43.5Z" }, + { url = "https://files.pythonhosted.org/packages/0b/19/97b491e3c51a39648775393a55196d129153abfa5f62105302b0cef0afe8/ajpegli-1.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:3b9b7b7b3ce9fd042da068d06ba2fb08c64595b2e0aba4420dcf0f49ece12bcd", size = 259641, upload-time = "2026-05-19T21:29:44.864Z" }, + { url = "https://files.pythonhosted.org/packages/61/9a/86e0cd693cf77290a587dafd2fc7942eb9384eecf5e196b6c3c9f36f3e83/ajpegli-1.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:7681ed14c9cc89a844de21c4feb824f504fca5dad08c89a264429dd35aea4c41", size = 277902, upload-time = "2026-05-19T21:29:46.428Z" }, + { url = "https://files.pythonhosted.org/packages/d9/22/f124f98e63da211e0b38dc9d155f8ddd84ffc2c64ddff2b5115456dbd80b/ajpegli-1.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d51799aa8786128738e864eb7c8770714864eb7840a6061cf6e90e9461f371d3", size = 197259, upload-time = "2026-05-19T21:29:47.879Z" }, + { url = "https://files.pythonhosted.org/packages/77/21/b4eb5a4b9a147829a8923d69f8dcd9c15dea972f025276773bae0d26f454/ajpegli-1.0.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1435aea7cb962eddd6e73206b7b3e49b2e7aa1a0c5daadcb9c8d42cecbf30df1", size = 287732, upload-time = "2026-05-19T21:29:49.292Z" }, + { url = "https://files.pythonhosted.org/packages/f2/53/1fa8bb7ce2a01e554bf905ae4fd2bd1ad99d4de33211e01c0401f91ce0a3/ajpegli-1.0.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:797f2deeaf71d43d4aab64eab2cdef93126d3cfdc7fadfdc16c78e01af5cba05", size = 344707, upload-time = "2026-05-19T21:29:50.971Z" }, + { url = "https://files.pythonhosted.org/packages/d6/ac/a825b22dcc8e7278773b1dc7c3d8aa0ddb3d04d3bb891176dd464becf4f6/ajpegli-1.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:d78157fb08e5718eb96d5438e28ca90a047d0fc4b0b32d6ca6b0b10a45c13811", size = 259717, upload-time = "2026-05-19T21:29:52.403Z" }, ] [[package]] name = "annotated-doc" -version = "0.0.4" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/57/ba/046ceea27344560984e26a590f90bc7f4a75b06701f653222458922b558c/annotated_doc-0.0.4.tar.gz", hash = "sha256:fbcda96e87e9c92ad167c2e53839e57503ecfda18804ea28102353485033faa4", size = 7288 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/1e/d3/26bf1008eb3d2daa8ef4cacc7f3bfdc11818d111f7e2d0201bc6e3b49d45/annotated_doc-0.0.4-py3-none-any.whl", hash = "sha256:571ac1dc6991c450b25a9c2d84a3705e2ae7a53467b5d111c24fa8baabbed320", size = 5303 }, +version = "0.0.5" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/5a/8e/38aa427ed5402449e226975b649c5dc73ccadfefeb95e6aecb8f8ea4b6b6/annotated_doc-0.0.5.tar.gz", hash = "sha256:c7e58ce09192557605d8bbd92836d7e1d520ac9580096042c0bfd197efacf1bb", size = 10758, upload-time = "2026-07-28T13:50:58.129Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3e/30/e900b21425a860e195f32e37657aa1f7c7f2b1bfb26f03ca209b90933c06/annotated_doc-0.0.5-py3-none-any.whl", hash = "sha256:117bac03a25ede5df5440e855b32d556049ca169ead221505badf432fed4b101", size = 5302, upload-time = "2026-07-28T13:50:57.239Z" }, +] + +[[package]] +name = "ast-serialize" +version = "0.6.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/58/ad/0d70a3a2d6e01968d985415259e8ec7ad3f777903f9b1c1f3c8c44642c60/ast_serialize-0.6.0.tar.gz", hash = "sha256:aadd3ffcf4858c9726bf3515f7b199c7eadbe504f96028e4a87172c0da65a8fe", size = 61489, upload-time = "2026-06-30T20:02:55.555Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3f/12/3e5f575f156555547c250a8b0d1347517a3a20fc7f4492e9703a69d4f45e/ast_serialize-0.6.0-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:a7520b672827885bafeae7501f684d14d47d17e5f45256f9df547686cca52264", size = 1177640, upload-time = "2026-06-30T20:02:06.708Z" }, + { url = "https://files.pythonhosted.org/packages/a2/a4/921a9e27951627983b0f368859ea00f8330a551dc0bf4c2fdcb11855a98b/ast_serialize-0.6.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:a14191beec7e0c078d2fc1f6edc0aee88bcd4db9f18e1bc9f8052b559c22dddc", size = 1168111, upload-time = "2026-06-30T20:02:08.366Z" }, + { url = "https://files.pythonhosted.org/packages/00/69/950cf404de7b8782cf95e5c1237e25e2aa46177b287f39f9eeddf481fd6f/ast_serialize-0.6.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:32ef62ec34cf6be20ad77d4799556638fbdf187f3ae10698dfb20ef9f2c89516", size = 1227656, upload-time = "2026-06-30T20:02:09.843Z" }, + { url = "https://files.pythonhosted.org/packages/4c/a8/46f8f6a6479d9d2273980957bb091a506c55f5b95d3c029ee58518a78407/ast_serialize-0.6.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:13b7769970a39983b0adf2f38917b1cd3b8946f76df045756c3d741bc689f089", size = 1227706, upload-time = "2026-06-30T20:02:11.367Z" }, + { url = "https://files.pythonhosted.org/packages/b7/b9/9ac415bda0a40e49eab8fea3b2741c19c98bb84d57d62c4cfc6230eb67be/ast_serialize-0.6.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6f7a408601bb3edaefb3bc67a4c01f5235e3253653b6a5729a2ee2382b35341c", size = 1431705, upload-time = "2026-06-30T20:02:12.737Z" }, + { url = "https://files.pythonhosted.org/packages/e5/06/8807115d441444879f7561b5eede5ac18fc80392f11826d61ccf31f503b1/ast_serialize-0.6.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8670bfa51208a2c0c8d138928e40e998fab158f9200d53bb80c088b5b8eda7b8", size = 1249533, upload-time = "2026-06-30T20:02:14.571Z" }, + { url = "https://files.pythonhosted.org/packages/3e/c0/c2ba82ef9618650357d9421a1fdb27ffec862a7f57e8e2de82a3ccd11e12/ast_serialize-0.6.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a4826809eb8597a8cd59fd924b6d7c285b8969a1e0007e2cb652cab62376270f", size = 1252619, upload-time = "2026-06-30T20:02:16.219Z" }, + { url = "https://files.pythonhosted.org/packages/0f/a7/fa31d52dd4102cede29fb9634e98d214129b2783b4f95528c6dc6a8f6587/ast_serialize-0.6.0-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:577a6c189068686869f5f1ddc38363f3ae1808a4753b577266f9202071a7bb66", size = 1242983, upload-time = "2026-06-30T20:02:17.813Z" }, + { url = "https://files.pythonhosted.org/packages/b1/20/ddf742b5ad3c4bafd3466f2265037cfd99bc1b9a5ee46a5d58c90d523242/ast_serialize-0.6.0-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:085de7f62dc9cc247eb01e965a362707d1d90b1d89a82c5bf78301a60a3c417b", size = 1296148, upload-time = "2026-06-30T20:02:19.146Z" }, + { url = "https://files.pythonhosted.org/packages/24/cb/9f6f217cce8b3b632c5568b478d195a35e79dce4dbe309438cb89ba6ea4f/ast_serialize-0.6.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:9f8a8b78b13173de6a9ec22111d9be674874cd5bdccda04f14ae5ebc2bef403a", size = 1403826, upload-time = "2026-06-30T20:02:20.696Z" }, + { url = "https://files.pythonhosted.org/packages/2d/f8/9d16d4f0107a183924425cc0e7618d8bf76f96b45afa9ff19f924ed1ad57/ast_serialize-0.6.0-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:f2ff3baffc3a29c1f15bc9098aa0c09763410262d5e6cef42116f7356c184554", size = 1502943, upload-time = "2026-06-30T20:02:22.034Z" }, + { url = "https://files.pythonhosted.org/packages/80/dd/bbc1c38756350dddf7e24acae1c9482ef42051c267417e019aecc1ed4075/ast_serialize-0.6.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:0067b25fce104eaae5b88383de9ab803faeb671831e14ca698b771b356e2600f", size = 1497632, upload-time = "2026-06-30T20:02:23.517Z" }, + { url = "https://files.pythonhosted.org/packages/42/7e/9daffefcf5b97e6bb4c3e0b3c024c1aee9722f23d3cf7cd2ff80d6fb4a40/ast_serialize-0.6.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:c617417f9cbb0cb144f6283c3cbe0d2e0f01beaf9f608f662b21191058a626ec", size = 1448858, upload-time = "2026-06-30T20:02:24.889Z" }, + { url = "https://files.pythonhosted.org/packages/e5/1f/f9baaab81a677ea0af7d2458cac2f94ebcc85958f8a3c15ba9d9e5dab653/ast_serialize-0.6.0-cp314-cp314t-win32.whl", hash = "sha256:5337cb256dcea3df9288205213d1601581536526b8f4da44b6974f1180f3252a", size = 1052600, upload-time = "2026-06-30T20:02:26.263Z" }, + { url = "https://files.pythonhosted.org/packages/9e/1f/41b535866519512d8cf6669cb2cff7823b7672bb6279c0333b4ff89d7d9f/ast_serialize-0.6.0-cp314-cp314t-win_amd64.whl", hash = "sha256:2d947e45cafc4b09bd7528917fa84c517654a43de173c79785574b7b3068ac24", size = 1095570, upload-time = "2026-06-30T20:02:27.639Z" }, + { url = "https://files.pythonhosted.org/packages/50/64/e472fe3e3a2d33d874b987e8518aedf24562919e3b6161a4fa1797e89c0f/ast_serialize-0.6.0-cp314-cp314t-win_arm64.whl", hash = "sha256:6e15ec740436e1a0d62de848641abe5f3a2f89a7f94907d534795ac91bbacf14", size = 1067267, upload-time = "2026-06-30T20:02:28.949Z" }, + { url = "https://files.pythonhosted.org/packages/52/19/ac8348ae8711c9b5ae834634f635780cab62a0f5e6f988882e048b89c2ae/ast_serialize-0.6.0-cp39-abi3-macosx_10_12_x86_64.whl", hash = "sha256:093cb8bb91b720d8523580498d031791bb1bbaa048599c3d21085d380e11a596", size = 1185367, upload-time = "2026-06-30T20:02:30.427Z" }, + { url = "https://files.pythonhosted.org/packages/c1/f6/ec7ec652c51db77c2f61d8573338e13e4704303265ccc658cb4031d9f354/ast_serialize-0.6.0-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:e61580a69faf47e3689795367ed211f2a10fd741478cc0f36a0f128793360aad", size = 1178657, upload-time = "2026-06-30T20:02:31.964Z" }, + { url = "https://files.pythonhosted.org/packages/6f/02/613a7534a41d0122f37d1e0c64aa8ac78bfb831f8c92f6db057a311abb3c/ast_serialize-0.6.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:305802f2ce2a7c4e87835078ea85c58b586ddda8095b92fe2ead9364ae19c80a", size = 1238620, upload-time = "2026-06-30T20:02:33.664Z" }, + { url = "https://files.pythonhosted.org/packages/4d/21/087957bba486242afc52f49b2d9e21c9dad00289356cf9efe67084015a9d/ast_serialize-0.6.0-cp39-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c7b8b8f0c42f752ea00b2b7d7c090b3f80d9c1c5c75cadf16423790a0cc74081", size = 1236075, upload-time = "2026-06-30T20:02:34.936Z" }, + { url = "https://files.pythonhosted.org/packages/82/04/78128bbb170071c2c72a210a181f1c00e11cc1cec60a8beef747b07f9201/ast_serialize-0.6.0-cp39-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cd5b91b9e6f2356ace3a556963b0cd783b395fbbb0bb17b4defc283415466e77", size = 1441348, upload-time = "2026-06-30T20:02:36.245Z" }, + { url = "https://files.pythonhosted.org/packages/64/64/62fb99d6faf199b4c3e5b08a07136e9a0d7664bb249c6de3670e5b63e9b6/ast_serialize-0.6.0-cp39-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4d6ef91590258ada18909b9caea344dac4de2013906b035473cd674a43f4b790", size = 1258580, upload-time = "2026-06-30T20:02:37.53Z" }, + { url = "https://files.pythonhosted.org/packages/ca/87/b4d6c38e0ccd5e85dc54cecdf933a152c60b28fe5d993a6d8a72fa6d5896/ast_serialize-0.6.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dcbed41e9386059fc0261d602445ede0976c2ecec2939688bcbcb9ed0b6f28b7", size = 1261693, upload-time = "2026-06-30T20:02:39.123Z" }, + { url = "https://files.pythonhosted.org/packages/0e/4b/3676ca2191f39bafb75f93f99b2f429ec464586158fece2165f3572805dc/ast_serialize-0.6.0-cp39-abi3-manylinux_2_31_riscv64.whl", hash = "sha256:cdc4e6f930b9090c2f92c9036ad12ffb8e6e44d4a5ba06f1458a05d60f203f7b", size = 1252517, upload-time = "2026-06-30T20:02:40.511Z" }, + { url = "https://files.pythonhosted.org/packages/f3/58/494ef8c4b4acb2f4a265ac934caf45f792a08fe27d6b853de35ad991941a/ast_serialize-0.6.0-cp39-abi3-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:897ac47b5637be41c0c07061c8a912fafa967ef1dc73fa115e4bfa70882a093b", size = 1304843, upload-time = "2026-06-30T20:02:41.961Z" }, + { url = "https://files.pythonhosted.org/packages/b1/f2/13736d920ab3d49bbee80ef1a277dd7b7aaf3b3545efd9d2a8114fe05525/ast_serialize-0.6.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:c4af9a1386166e40ed01464991806f89038a2d89782576c7774876fa77034e32", size = 1413698, upload-time = "2026-06-30T20:02:44.179Z" }, + { url = "https://files.pythonhosted.org/packages/a8/5a/e046f3899e2acba4677d7427b76431443a1aa1a0e583dfb05b55b69d55cf/ast_serialize-0.6.0-cp39-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:c901adbd750029b9ac4ad3d6aa56853e0ad4875119fbf52b7b8298afc223828b", size = 1512209, upload-time = "2026-06-30T20:02:45.584Z" }, + { url = "https://files.pythonhosted.org/packages/cc/c7/e42aaca7bb2d22a7c06d5a8c7930086c5a334e93d716e6fa5e6647a4515f/ast_serialize-0.6.0-cp39-abi3-musllinux_1_2_i686.whl", hash = "sha256:3ae22a366b752ab4496191525b78b097b5b72d531752e3c1dd7e383a8f2c8a1a", size = 1508464, upload-time = "2026-06-30T20:02:46.942Z" }, + { url = "https://files.pythonhosted.org/packages/95/93/5524a3dc6c3f593de3228ed9cbef73afa047625b7000ec21b7f58e6eb4d4/ast_serialize-0.6.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:4ed29121da8b3fdc291002801a1de0f76248fa07dce89157a5f277842cf6126e", size = 1457164, upload-time = "2026-06-30T20:02:48.294Z" }, + { url = "https://files.pythonhosted.org/packages/4f/c0/36a6ffb4d653cf621427b4c4928671f53ad800c453474de2b82564a44ad9/ast_serialize-0.6.0-cp39-abi3-pyemscripten_2026_0_wasm32.whl", hash = "sha256:b1dac4e09d341c1300ba69cdcbe62867b32a8c75d90db9bf4d083bec3b039f0b", size = 863014, upload-time = "2026-06-30T20:02:49.742Z" }, + { url = "https://files.pythonhosted.org/packages/09/c7/7d5ad8b49e1278e1c2a1e0274bd7850560b3f09313aa00c13bc8d5544792/ast_serialize-0.6.0-cp39-abi3-win32.whl", hash = "sha256:82c312a7844d2fdeb4d5c48bd3d215bf940dafd4704e1a9bcf252a99010a99b1", size = 1063165, upload-time = "2026-06-30T20:02:50.98Z" }, + { url = "https://files.pythonhosted.org/packages/47/ae/6710c14ecb276031cf10249f6adf5a59e2d3fdb3b5183bd59f70524067ee/ast_serialize-0.6.0-cp39-abi3-win_amd64.whl", hash = "sha256:113b58346f9ceb664352032770caca817d4a3c86f611c6088e6ef65ddaa70f0e", size = 1101444, upload-time = "2026-06-30T20:02:52.554Z" }, + { url = "https://files.pythonhosted.org/packages/66/40/c53deb2cd0c9b0fb636d24d9f40924cf2e65028e6b20b10cd5c1eeb2c730/ast_serialize-0.6.0-cp39-abi3-win_arm64.whl", hash = "sha256:ccd132fe8db56f61fe743b1f644d01b8d65b83248a8da506f3132bda86d6ed5e", size = 1072965, upload-time = "2026-06-30T20:02:54.097Z" }, ] [[package]] @@ -72,207 +118,217 @@ dependencies = [ { name = "six" }, { name = "wheel" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/f3/af/4182184d3c338792894f34a62672919db7ca008c89abee9b564dd34d8029/astunparse-1.6.3.tar.gz", hash = "sha256:5ad93a8456f0d084c3456d059fd9a92cce667963232cbf763eac3bc5b7940872", size = 18290 } +sdist = { url = "https://files.pythonhosted.org/packages/f3/af/4182184d3c338792894f34a62672919db7ca008c89abee9b564dd34d8029/astunparse-1.6.3.tar.gz", hash = "sha256:5ad93a8456f0d084c3456d059fd9a92cce667963232cbf763eac3bc5b7940872", size = 18290, upload-time = "2019-12-22T18:12:13.129Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/2b/03/13dde6512ad7b4557eb792fbcf0c653af6076b81e5941d36ec61f7ce6028/astunparse-1.6.3-py2.py3-none-any.whl", hash = "sha256:c2652417f2c8b5bb325c885ae329bdf3f86424075c4fd1a128674bc6fba4b8e8", size = 12732 }, + { url = "https://files.pythonhosted.org/packages/2b/03/13dde6512ad7b4557eb792fbcf0c653af6076b81e5941d36ec61f7ce6028/astunparse-1.6.3-py2.py3-none-any.whl", hash = "sha256:c2652417f2c8b5bb325c885ae329bdf3f86424075c4fd1a128674bc6fba4b8e8", size = 12732, upload-time = "2019-12-22T18:12:11.297Z" }, +] + +[[package]] +name = "attrs" +version = "26.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/9a/8e/82a0fe20a541c03148528be8cac2408564a6c9a0cc7e9171802bc1d26985/attrs-26.1.0.tar.gz", hash = "sha256:d03ceb89cb322a8fd706d4fb91940737b6642aa36998fe130a9bc96c985eff32", size = 952055, upload-time = "2026-03-19T14:22:25.026Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/64/b4/17d4b0b2a2dc85a6df63d1157e028ed19f90d4cd97c36717afef2bc2f395/attrs-26.1.0-py3-none-any.whl", hash = "sha256:c647aa4a12dfbad9333ca4e71fe62ddc36f4e63b2d260a37a8b83d2f043ac309", size = 67548, upload-time = "2026-03-19T14:22:23.645Z" }, ] [[package]] name = "certifi" -version = "2026.2.25" +version = "2026.7.22" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/af/2d/7bf41579a8986e348fa033a31cdd0e4121114f6bce2457e8876010b092dd/certifi-2026.2.25.tar.gz", hash = "sha256:e887ab5cee78ea814d3472169153c2d12cd43b14bd03329a39a9c6e2e80bfba7", size = 155029 } +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/9a/3c/c17fb3ca2d9c3acff52e30b309f538586f9f5b9c9cf454f3845fc9af4881/certifi-2026.2.25-py3-none-any.whl", hash = "sha256:027692e4402ad994f1c42e52a4997a9763c646b73e4096e4d5d6db8af1d6f0fa", size = 153684 }, + { 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 = "cffi" -version = "2.0.0" +version = "2.1.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pycparser", marker = "implementation_name != 'PyPy'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/eb/56/b1ba7935a17738ae8453301356628e8147c79dbb825bcbc73dc7401f9846/cffi-2.0.0.tar.gz", hash = "sha256:44d1b5909021139fe36001ae048dbdde8214afa20200eda0f64c068cac5d5529", size = 523588 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/12/4a/3dfd5f7850cbf0d06dc84ba9aa00db766b52ca38d8b86e3a38314d52498c/cffi-2.0.0-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:b4c854ef3adc177950a8dfc81a86f5115d2abd545751a304c5bcf2c2c7283cfe", size = 184344 }, - { url = "https://files.pythonhosted.org/packages/4f/8b/f0e4c441227ba756aafbe78f117485b25bb26b1c059d01f137fa6d14896b/cffi-2.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2de9a304e27f7596cd03d16f1b7c72219bd944e99cc52b84d0145aefb07cbd3c", size = 180560 }, - { url = "https://files.pythonhosted.org/packages/b1/b7/1200d354378ef52ec227395d95c2576330fd22a869f7a70e88e1447eb234/cffi-2.0.0-cp311-cp311-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:baf5215e0ab74c16e2dd324e8ec067ef59e41125d3eade2b863d294fd5035c92", size = 209613 }, - { url = "https://files.pythonhosted.org/packages/b8/56/6033f5e86e8cc9bb629f0077ba71679508bdf54a9a5e112a3c0b91870332/cffi-2.0.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:730cacb21e1bdff3ce90babf007d0a0917cc3e6492f336c2f0134101e0944f93", size = 216476 }, - { url = "https://files.pythonhosted.org/packages/dc/7f/55fecd70f7ece178db2f26128ec41430d8720f2d12ca97bf8f0a628207d5/cffi-2.0.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:6824f87845e3396029f3820c206e459ccc91760e8fa24422f8b0c3d1731cbec5", size = 203374 }, - { url = "https://files.pythonhosted.org/packages/84/ef/a7b77c8bdc0f77adc3b46888f1ad54be8f3b7821697a7b89126e829e676a/cffi-2.0.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:9de40a7b0323d889cf8d23d1ef214f565ab154443c42737dfe52ff82cf857664", size = 202597 }, - { url = "https://files.pythonhosted.org/packages/d7/91/500d892b2bf36529a75b77958edfcd5ad8e2ce4064ce2ecfeab2125d72d1/cffi-2.0.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8941aaadaf67246224cee8c3803777eed332a19d909b47e29c9842ef1e79ac26", size = 215574 }, - { url = "https://files.pythonhosted.org/packages/44/64/58f6255b62b101093d5df22dcb752596066c7e89dd725e0afaed242a61be/cffi-2.0.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:a05d0c237b3349096d3981b727493e22147f934b20f6f125a3eba8f994bec4a9", size = 218971 }, - { url = "https://files.pythonhosted.org/packages/ab/49/fa72cebe2fd8a55fbe14956f9970fe8eb1ac59e5df042f603ef7c8ba0adc/cffi-2.0.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:94698a9c5f91f9d138526b48fe26a199609544591f859c870d477351dc7b2414", size = 211972 }, - { url = "https://files.pythonhosted.org/packages/0b/28/dd0967a76aab36731b6ebfe64dec4e981aff7e0608f60c2d46b46982607d/cffi-2.0.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:5fed36fccc0612a53f1d4d9a816b50a36702c28a2aa880cb8a122b3466638743", size = 217078 }, - { url = "https://files.pythonhosted.org/packages/2b/c0/015b25184413d7ab0a410775fdb4a50fca20f5589b5dab1dbbfa3baad8ce/cffi-2.0.0-cp311-cp311-win32.whl", hash = "sha256:c649e3a33450ec82378822b3dad03cc228b8f5963c0c12fc3b1e0ab940f768a5", size = 172076 }, - { url = "https://files.pythonhosted.org/packages/ae/8f/dc5531155e7070361eb1b7e4c1a9d896d0cb21c49f807a6c03fd63fc877e/cffi-2.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:66f011380d0e49ed280c789fbd08ff0d40968ee7b665575489afa95c98196ab5", size = 182820 }, - { url = "https://files.pythonhosted.org/packages/95/5c/1b493356429f9aecfd56bc171285a4c4ac8697f76e9bbbbb105e537853a1/cffi-2.0.0-cp311-cp311-win_arm64.whl", hash = "sha256:c6638687455baf640e37344fe26d37c404db8b80d037c3d29f58fe8d1c3b194d", size = 177635 }, - { url = "https://files.pythonhosted.org/packages/ea/47/4f61023ea636104d4f16ab488e268b93008c3d0bb76893b1b31db1f96802/cffi-2.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6d02d6655b0e54f54c4ef0b94eb6be0607b70853c45ce98bd278dc7de718be5d", size = 185271 }, - { url = "https://files.pythonhosted.org/packages/df/a2/781b623f57358e360d62cdd7a8c681f074a71d445418a776eef0aadb4ab4/cffi-2.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8eca2a813c1cb7ad4fb74d368c2ffbbb4789d377ee5bb8df98373c2cc0dee76c", size = 181048 }, - { url = "https://files.pythonhosted.org/packages/ff/df/a4f0fbd47331ceeba3d37c2e51e9dfc9722498becbeec2bd8bc856c9538a/cffi-2.0.0-cp312-cp312-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:21d1152871b019407d8ac3985f6775c079416c282e431a4da6afe7aefd2bccbe", size = 212529 }, - { url = "https://files.pythonhosted.org/packages/d5/72/12b5f8d3865bf0f87cf1404d8c374e7487dcf097a1c91c436e72e6badd83/cffi-2.0.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b21e08af67b8a103c71a250401c78d5e0893beff75e28c53c98f4de42f774062", size = 220097 }, - { url = "https://files.pythonhosted.org/packages/c2/95/7a135d52a50dfa7c882ab0ac17e8dc11cec9d55d2c18dda414c051c5e69e/cffi-2.0.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:1e3a615586f05fc4065a8b22b8152f0c1b00cdbc60596d187c2a74f9e3036e4e", size = 207983 }, - { url = "https://files.pythonhosted.org/packages/3a/c8/15cb9ada8895957ea171c62dc78ff3e99159ee7adb13c0123c001a2546c1/cffi-2.0.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:81afed14892743bbe14dacb9e36d9e0e504cd204e0b165062c488942b9718037", size = 206519 }, - { url = "https://files.pythonhosted.org/packages/78/2d/7fa73dfa841b5ac06c7b8855cfc18622132e365f5b81d02230333ff26e9e/cffi-2.0.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3e17ed538242334bf70832644a32a7aae3d83b57567f9fd60a26257e992b79ba", size = 219572 }, - { url = "https://files.pythonhosted.org/packages/07/e0/267e57e387b4ca276b90f0434ff88b2c2241ad72b16d31836adddfd6031b/cffi-2.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3925dd22fa2b7699ed2617149842d2e6adde22b262fcbfada50e3d195e4b3a94", size = 222963 }, - { url = "https://files.pythonhosted.org/packages/b6/75/1f2747525e06f53efbd878f4d03bac5b859cbc11c633d0fb81432d98a795/cffi-2.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2c8f814d84194c9ea681642fd164267891702542f028a15fc97d4674b6206187", size = 221361 }, - { url = "https://files.pythonhosted.org/packages/7b/2b/2b6435f76bfeb6bbf055596976da087377ede68df465419d192acf00c437/cffi-2.0.0-cp312-cp312-win32.whl", hash = "sha256:da902562c3e9c550df360bfa53c035b2f241fed6d9aef119048073680ace4a18", size = 172932 }, - { url = "https://files.pythonhosted.org/packages/f8/ed/13bd4418627013bec4ed6e54283b1959cf6db888048c7cf4b4c3b5b36002/cffi-2.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:da68248800ad6320861f129cd9c1bf96ca849a2771a59e0344e88681905916f5", size = 183557 }, - { url = "https://files.pythonhosted.org/packages/95/31/9f7f93ad2f8eff1dbc1c3656d7ca5bfd8fb52c9d786b4dcf19b2d02217fa/cffi-2.0.0-cp312-cp312-win_arm64.whl", hash = "sha256:4671d9dd5ec934cb9a73e7ee9676f9362aba54f7f34910956b84d727b0d73fb6", size = 177762 }, - { url = "https://files.pythonhosted.org/packages/4b/8d/a0a47a0c9e413a658623d014e91e74a50cdd2c423f7ccfd44086ef767f90/cffi-2.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:00bdf7acc5f795150faa6957054fbbca2439db2f775ce831222b66f192f03beb", size = 185230 }, - { url = "https://files.pythonhosted.org/packages/4a/d2/a6c0296814556c68ee32009d9c2ad4f85f2707cdecfd7727951ec228005d/cffi-2.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:45d5e886156860dc35862657e1494b9bae8dfa63bf56796f2fb56e1679fc0bca", size = 181043 }, - { url = "https://files.pythonhosted.org/packages/b0/1e/d22cc63332bd59b06481ceaac49d6c507598642e2230f201649058a7e704/cffi-2.0.0-cp313-cp313-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:07b271772c100085dd28b74fa0cd81c8fb1a3ba18b21e03d7c27f3436a10606b", size = 212446 }, - { url = "https://files.pythonhosted.org/packages/a9/f5/a2c23eb03b61a0b8747f211eb716446c826ad66818ddc7810cc2cc19b3f2/cffi-2.0.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d48a880098c96020b02d5a1f7d9251308510ce8858940e6fa99ece33f610838b", size = 220101 }, - { url = "https://files.pythonhosted.org/packages/f2/7f/e6647792fc5850d634695bc0e6ab4111ae88e89981d35ac269956605feba/cffi-2.0.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:f93fd8e5c8c0a4aa1f424d6173f14a892044054871c771f8566e4008eaa359d2", size = 207948 }, - { url = "https://files.pythonhosted.org/packages/cb/1e/a5a1bd6f1fb30f22573f76533de12a00bf274abcdc55c8edab639078abb6/cffi-2.0.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:dd4f05f54a52fb558f1ba9f528228066954fee3ebe629fc1660d874d040ae5a3", size = 206422 }, - { url = "https://files.pythonhosted.org/packages/98/df/0a1755e750013a2081e863e7cd37e0cdd02664372c754e5560099eb7aa44/cffi-2.0.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c8d3b5532fc71b7a77c09192b4a5a200ea992702734a2e9279a37f2478236f26", size = 219499 }, - { url = "https://files.pythonhosted.org/packages/50/e1/a969e687fcf9ea58e6e2a928ad5e2dd88cc12f6f0ab477e9971f2309b57c/cffi-2.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d9b29c1f0ae438d5ee9acb31cadee00a58c46cc9c0b2f9038c6b0b3470877a8c", size = 222928 }, - { url = "https://files.pythonhosted.org/packages/36/54/0362578dd2c9e557a28ac77698ed67323ed5b9775ca9d3fe73fe191bb5d8/cffi-2.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6d50360be4546678fc1b79ffe7a66265e28667840010348dd69a314145807a1b", size = 221302 }, - { url = "https://files.pythonhosted.org/packages/eb/6d/bf9bda840d5f1dfdbf0feca87fbdb64a918a69bca42cfa0ba7b137c48cb8/cffi-2.0.0-cp313-cp313-win32.whl", hash = "sha256:74a03b9698e198d47562765773b4a8309919089150a0bb17d829ad7b44b60d27", size = 172909 }, - { url = "https://files.pythonhosted.org/packages/37/18/6519e1ee6f5a1e579e04b9ddb6f1676c17368a7aba48299c3759bbc3c8b3/cffi-2.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:19f705ada2530c1167abacb171925dd886168931e0a7b78f5bffcae5c6b5be75", size = 183402 }, - { url = "https://files.pythonhosted.org/packages/cb/0e/02ceeec9a7d6ee63bb596121c2c8e9b3a9e150936f4fbef6ca1943e6137c/cffi-2.0.0-cp313-cp313-win_arm64.whl", hash = "sha256:256f80b80ca3853f90c21b23ee78cd008713787b1b1e93eae9f3d6a7134abd91", size = 177780 }, - { url = "https://files.pythonhosted.org/packages/92/c4/3ce07396253a83250ee98564f8d7e9789fab8e58858f35d07a9a2c78de9f/cffi-2.0.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:fc33c5141b55ed366cfaad382df24fe7dcbc686de5be719b207bb248e3053dc5", size = 185320 }, - { url = "https://files.pythonhosted.org/packages/59/dd/27e9fa567a23931c838c6b02d0764611c62290062a6d4e8ff7863daf9730/cffi-2.0.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c654de545946e0db659b3400168c9ad31b5d29593291482c43e3564effbcee13", size = 181487 }, - { url = "https://files.pythonhosted.org/packages/d6/43/0e822876f87ea8a4ef95442c3d766a06a51fc5298823f884ef87aaad168c/cffi-2.0.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:24b6f81f1983e6df8db3adc38562c83f7d4a0c36162885ec7f7b77c7dcbec97b", size = 220049 }, - { url = "https://files.pythonhosted.org/packages/b4/89/76799151d9c2d2d1ead63c2429da9ea9d7aac304603de0c6e8764e6e8e70/cffi-2.0.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:12873ca6cb9b0f0d3a0da705d6086fe911591737a59f28b7936bdfed27c0d47c", size = 207793 }, - { url = "https://files.pythonhosted.org/packages/bb/dd/3465b14bb9e24ee24cb88c9e3730f6de63111fffe513492bf8c808a3547e/cffi-2.0.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:d9b97165e8aed9272a6bb17c01e3cc5871a594a446ebedc996e2397a1c1ea8ef", size = 206300 }, - { url = "https://files.pythonhosted.org/packages/47/d9/d83e293854571c877a92da46fdec39158f8d7e68da75bf73581225d28e90/cffi-2.0.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:afb8db5439b81cf9c9d0c80404b60c3cc9c3add93e114dcae767f1477cb53775", size = 219244 }, - { url = "https://files.pythonhosted.org/packages/2b/0f/1f177e3683aead2bb00f7679a16451d302c436b5cbf2505f0ea8146ef59e/cffi-2.0.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:737fe7d37e1a1bffe70bd5754ea763a62a066dc5913ca57e957824b72a85e205", size = 222828 }, - { url = "https://files.pythonhosted.org/packages/c6/0f/cafacebd4b040e3119dcb32fed8bdef8dfe94da653155f9d0b9dc660166e/cffi-2.0.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:38100abb9d1b1435bc4cc340bb4489635dc2f0da7456590877030c9b3d40b0c1", size = 220926 }, - { url = "https://files.pythonhosted.org/packages/3e/aa/df335faa45b395396fcbc03de2dfcab242cd61a9900e914fe682a59170b1/cffi-2.0.0-cp314-cp314-win32.whl", hash = "sha256:087067fa8953339c723661eda6b54bc98c5625757ea62e95eb4898ad5e776e9f", size = 175328 }, - { url = "https://files.pythonhosted.org/packages/bb/92/882c2d30831744296ce713f0feb4c1cd30f346ef747b530b5318715cc367/cffi-2.0.0-cp314-cp314-win_amd64.whl", hash = "sha256:203a48d1fb583fc7d78a4c6655692963b860a417c0528492a6bc21f1aaefab25", size = 185650 }, - { url = "https://files.pythonhosted.org/packages/9f/2c/98ece204b9d35a7366b5b2c6539c350313ca13932143e79dc133ba757104/cffi-2.0.0-cp314-cp314-win_arm64.whl", hash = "sha256:dbd5c7a25a7cb98f5ca55d258b103a2054f859a46ae11aaf23134f9cc0d356ad", size = 180687 }, - { url = "https://files.pythonhosted.org/packages/3e/61/c768e4d548bfa607abcda77423448df8c471f25dbe64fb2ef6d555eae006/cffi-2.0.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:9a67fc9e8eb39039280526379fb3a70023d77caec1852002b4da7e8b270c4dd9", size = 188773 }, - { url = "https://files.pythonhosted.org/packages/2c/ea/5f76bce7cf6fcd0ab1a1058b5af899bfbef198bea4d5686da88471ea0336/cffi-2.0.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7a66c7204d8869299919db4d5069a82f1561581af12b11b3c9f48c584eb8743d", size = 185013 }, - { url = "https://files.pythonhosted.org/packages/be/b4/c56878d0d1755cf9caa54ba71e5d049479c52f9e4afc230f06822162ab2f/cffi-2.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7cc09976e8b56f8cebd752f7113ad07752461f48a58cbba644139015ac24954c", size = 221593 }, - { url = "https://files.pythonhosted.org/packages/e0/0d/eb704606dfe8033e7128df5e90fee946bbcb64a04fcdaa97321309004000/cffi-2.0.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:92b68146a71df78564e4ef48af17551a5ddd142e5190cdf2c5624d0c3ff5b2e8", size = 209354 }, - { url = "https://files.pythonhosted.org/packages/d8/19/3c435d727b368ca475fb8742ab97c9cb13a0de600ce86f62eab7fa3eea60/cffi-2.0.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:b1e74d11748e7e98e2f426ab176d4ed720a64412b6a15054378afdb71e0f37dc", size = 208480 }, - { url = "https://files.pythonhosted.org/packages/d0/44/681604464ed9541673e486521497406fadcc15b5217c3e326b061696899a/cffi-2.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:28a3a209b96630bca57cce802da70c266eb08c6e97e5afd61a75611ee6c64592", size = 221584 }, - { url = "https://files.pythonhosted.org/packages/25/8e/342a504ff018a2825d395d44d63a767dd8ebc927ebda557fecdaca3ac33a/cffi-2.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:7553fb2090d71822f02c629afe6042c299edf91ba1bf94951165613553984512", size = 224443 }, - { url = "https://files.pythonhosted.org/packages/e1/5e/b666bacbbc60fbf415ba9988324a132c9a7a0448a9a8f125074671c0f2c3/cffi-2.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:6c6c373cfc5c83a975506110d17457138c8c63016b563cc9ed6e056a82f13ce4", size = 223437 }, - { url = "https://files.pythonhosted.org/packages/a0/1d/ec1a60bd1a10daa292d3cd6bb0b359a81607154fb8165f3ec95fe003b85c/cffi-2.0.0-cp314-cp314t-win32.whl", hash = "sha256:1fc9ea04857caf665289b7a75923f2c6ed559b8298a1b8c49e59f7dd95c8481e", size = 180487 }, - { url = "https://files.pythonhosted.org/packages/bf/41/4c1168c74fac325c0c8156f04b6749c8b6a8f405bbf91413ba088359f60d/cffi-2.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:d68b6cef7827e8641e8ef16f4494edda8b36104d79773a334beaa1e3521430f6", size = 191726 }, - { url = "https://files.pythonhosted.org/packages/ae/3a/dbeec9d1ee0844c679f6bb5d6ad4e9f198b1224f4e7a32825f47f6192b0c/cffi-2.0.0-cp314-cp314t-win_arm64.whl", hash = "sha256:0a1527a803f0a659de1af2e1fd700213caba79377e27e4693648c2923da066f9", size = 184195 }, +sdist = { url = "https://files.pythonhosted.org/packages/57/5f/ff100cae70ebe9d8df1c01a00e510e45d9adb5c1fdda84791b199141de97/cffi-2.1.0.tar.gz", hash = "sha256:efc1cdd798b1aaf39b4610bba7aad28c9bea9b910f25c784ccf9ec1fa719d1f9", size = 531036, upload-time = "2026-07-06T21:34:30.382Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d3/67/85c89a59ba36a671e79638f44d466749f08179266a57e4f2ffdf92174072/cffi-2.1.0-cp311-cp311-macosx_10_15_x86_64.whl", hash = "sha256:02cb7ff33ded4f1532476731f89ede53e2e488a8e6205515a82144246ffa7dcc", size = 183845, upload-time = "2026-07-06T21:32:26.32Z" }, + { url = "https://files.pythonhosted.org/packages/ea/dd/e3b0baa2d3d6a857ac72b7efbf18e32e487c9cdafcc13049ad765495b15e/cffi-2.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f5bce581e6b8c235e566a14768a943b172ada3ed73537bb0c0be1edee312d4e7", size = 184186, upload-time = "2026-07-06T21:32:28.025Z" }, + { url = "https://files.pythonhosted.org/packages/65/68/9f3ef890cf3c6ab97bd531c5677f67613d302165d16f8142b2811782a614/cffi-2.1.0-cp311-cp311-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:30b65779d598c370374fefabf138d456fd6f3216bfa7bedfab1ba82025b0cd93", size = 211892, upload-time = "2026-07-06T21:32:29.565Z" }, + { url = "https://files.pythonhosted.org/packages/22/d7/1a74539db16d8bfd839ff1515948948efbb162e574650fd3d846896eea95/cffi-2.1.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:88023dfe18799507b73f1dbb0d14326a17465de1bc9c9c7655c22845e9ddc3a2", size = 218793, upload-time = "2026-07-06T21:32:30.951Z" }, + { url = "https://files.pythonhosted.org/packages/ec/d1/9a5b7169499e8e8d8e636de70b97ac7c9447104d2ff1a2cd94790cea5162/cffi-2.1.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:0a96b74cda968eebbad56d973efe5098974f0a9fb323865bf99ea1fd24e3e64c", size = 205737, upload-time = "2026-07-06T21:32:32.216Z" }, + { url = "https://files.pythonhosted.org/packages/ba/b0/e131a9c41f10607926278453d9596163594fe1c4ebc46efe3b5e5b34eb84/cffi-2.1.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:a5781494d4d400a3f47f8f1da94b324f6e6b440a53387774002890a2a2f4b50f", size = 204909, upload-time = "2026-07-06T21:32:33.655Z" }, + { url = "https://files.pythonhosted.org/packages/fb/d2/4398416cd699b35167947c6e22aca52c47e69ad5695073c9f1f2c52e04aa/cffi-2.1.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:aa7a1b53a2a4452ada2d1b5dade9960b2522f1e61293a811a077439e39029565", size = 217883, upload-time = "2026-07-06T21:32:35.173Z" }, + { url = "https://files.pythonhosted.org/packages/a2/a5/d4fe77b589e5e82d43ebc809bf2e6474afe8e48e32ea050b9357645b6471/cffi-2.1.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:9d8272c0e483b024e1b9ad029821470ed8ec65631dbd90217469da0e7cd89f1c", size = 221251, upload-time = "2026-07-06T21:32:36.527Z" }, + { url = "https://files.pythonhosted.org/packages/22/f0/a2fc43084c0433caf7f461bccc013e28f848d04ee1c5ed7fce71423cf4d9/cffi-2.1.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:7762faa47e8ff7eb80bd261d9a7d8eea2d8baa69de5e95b70c1f338bbe712f02", size = 214250, upload-time = "2026-07-06T21:32:37.852Z" }, + { url = "https://files.pythonhosted.org/packages/04/8c/b925975448cf20634a9fbd5efceb807219db452653648d2897c0989cab2d/cffi-2.1.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:89095c1968b4ba8285840e131bf2891b09ae137fe2146905acae0354fbce1b5e", size = 219441, upload-time = "2026-07-06T21:32:39.146Z" }, + { url = "https://files.pythonhosted.org/packages/eb/da/5c4918a2d61d86fa927d716cb3d8e4626ef8dc8f605a599d32f33897f59a/cffi-2.1.0-cp311-cp311-win32.whl", hash = "sha256:64c753a0f87a256020004f37a1c8c02c480e725f910f0b2a0f3f07debd1b2479", size = 174496, upload-time = "2026-07-06T21:32:40.467Z" }, + { url = "https://files.pythonhosted.org/packages/f9/c8/6c2de1d55cf35ef8b92885d5ef280790f0fb9634d87ea1cc315176aecd61/cffi-2.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:4f26194e3d95e06501b942642855aed4f953d55e95d7d01b7c4483db3ecff458", size = 185113, upload-time = "2026-07-06T21:32:41.761Z" }, + { url = "https://files.pythonhosted.org/packages/9e/4e/e8d7cb5783f1841a3c8fb3a7735838d7484d08ec08c9f984b14cac1ac0e9/cffi-2.1.0-cp311-cp311-win_arm64.whl", hash = "sha256:35aaea0c7ee0e58a5cd8c2fd1a48fdf7ece0d2699b7ecdda08194e9ce5dd9b3d", size = 179927, upload-time = "2026-07-06T21:32:42.961Z" }, + { url = "https://files.pythonhosted.org/packages/1e/85/990925db5df586ec90beb97529c853497e7f85ba0234830447faf41c3057/cffi-2.1.0-cp312-cp312-macosx_10_15_x86_64.whl", hash = "sha256:df2b82571a1b30f58a87bf4e5a9e78d2b1eff6c6ce8fd3aa3757221f93f0863f", size = 184829, upload-time = "2026-07-06T21:32:44.324Z" }, + { url = "https://files.pythonhosted.org/packages/4b/92/e7bb136ad6b5352603732cf907ef862ca103f20f2031c1735a46300c20c9/cffi-2.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:78474632761faa0fb96f30b1c928c84ebcf68713cbb80d15bab09dfe61640fde", size = 184728, upload-time = "2026-07-06T21:32:45.683Z" }, + { url = "https://files.pythonhosted.org/packages/c3/c0/d1ec30ffb370f748f2fb54425972bfef9871e0132e82fb589c46b6676049/cffi-2.1.0-cp312-cp312-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:5972433ad71a9e46516584ef60a0fda12d9dc459938d1539c3ddecf9bdc1368d", size = 214815, upload-time = "2026-07-06T21:32:48.557Z" }, + { url = "https://files.pythonhosted.org/packages/1b/dc/5620cf930688be01f2d673804291de757a934c90b946dbdc3d84130c2ea4/cffi-2.1.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b6422532152adf4e59b110cb2808cee7a033800952f5c036b4af047ee43199e7", size = 222429, upload-time = "2026-07-06T21:32:49.848Z" }, + { url = "https://files.pythonhosted.org/packages/4b/a4/77b53abbf7a1e0beb9637edbef2a94d15f9c822f591e85d439ffd91519a6/cffi-2.1.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:46b1c8db8f6122420f32d02fffb924c2fe9bc772d228c7c711748fff56aabb2b", size = 210315, upload-time = "2026-07-06T21:32:51.221Z" }, + { url = "https://files.pythonhosted.org/packages/58/0c/f528df19cc94b675087324d4760d9e6d5bfae97d6217aa4fac43de4f5fcc/cffi-2.1.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:d9fafc5aa2e2a39aaf7f8cc0c1f044a9b07fca12e558dca53a3cc5c654ad67a7", size = 208859, upload-time = "2026-07-06T21:32:52.512Z" }, + { url = "https://files.pythonhosted.org/packages/62/f2/c9522a81c32132799a1972c39f5c5f8b4c8b9f00488a23feaa6c06f07741/cffi-2.1.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1e9f50d192a3e525b15a75ab5114e442d83d657b7ec29182a991bc9a88fd3a66", size = 221844, upload-time = "2026-07-06T21:32:53.704Z" }, + { url = "https://files.pythonhosted.org/packages/6e/28/bd53988b9833e8f8ad539d26f4c07a6b3f6bcb1e9e02e7ca038250b3428d/cffi-2.1.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:98fff996e983a36d3aa2eca83af40c5821202e7e6f32d13ae94e3d2286f10cfe", size = 225287, upload-time = "2026-07-06T21:32:54.907Z" }, + { url = "https://files.pythonhosted.org/packages/79/99/0d0fd37f055224085f42bbb2c022d002e17dde4a97972822327b07d84101/cffi-2.1.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:379de10ce1ba048b1448599d1b37b24caee16309d1ac98d3982fc997f768700b", size = 223681, upload-time = "2026-07-06T21:32:56.329Z" }, + { url = "https://files.pythonhosted.org/packages/b0/80/c138990aa2a70b1a269f6e06348729836d733d6f970867943f61d367f8cc/cffi-2.1.0-cp312-cp312-win32.whl", hash = "sha256:9b8f0f26ca4e7513c534d351eca551947d053fac438f2a04ac96d882909b0d3a", size = 175269, upload-time = "2026-07-06T21:32:57.777Z" }, + { url = "https://files.pythonhosted.org/packages/a8/eb/f636456ff21a83fc13c032b58cc5dde061691546ac79efa284b2989b7982/cffi-2.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:c97f080ea627e2863524c5af3836e2270b5f5dfff1f104392b959f8df0c5d384", size = 185881, upload-time = "2026-07-06T21:32:59.253Z" }, + { url = "https://files.pythonhosted.org/packages/dd/2c/400ea43e721727dca8a65c4521390e9196757caba4a45643acb2b63271b8/cffi-2.1.0-cp312-cp312-win_arm64.whl", hash = "sha256:6d194185eabd279f1c05ebe3504265ddfc5ad2b58d0714f7db9f01da592e9eb6", size = 180088, upload-time = "2026-07-06T21:33:02.278Z" }, + { url = "https://files.pythonhosted.org/packages/96/88/a996879e2eeccb815f6e3a5967b12a308257412acec882039d386bd2aa7b/cffi-2.1.0-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:10537b1df4967ca26d21e5072d7d54188354483b91dc75058968d3f0cf13fbda", size = 194331, upload-time = "2026-07-06T21:33:03.697Z" }, + { url = "https://files.pythonhosted.org/packages/58/85/7ae00d5c8dd6266f4e944c3db630f3c5c9a98b61d469c714d848b1d8138a/cffi-2.1.0-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:a95b05f9baf29b91171b3a8bd2020b028835243e7b0ff6bb23e2a3c228518b1b", size = 196966, upload-time = "2026-07-06T21:33:05.353Z" }, + { url = "https://files.pythonhosted.org/packages/8c/e9/45c3a76ad8d43ad9261f4c95436da61128d3ca545d72b9612c0ab5be0b1c/cffi-2.1.0-cp313-cp313-macosx_10_15_x86_64.whl", hash = "sha256:15faec4adfff450819f3aee0e2e02c812de6edb88203aa58807955db2003472a", size = 184795, upload-time = "2026-07-06T21:33:06.699Z" }, + { url = "https://files.pythonhosted.org/packages/84/4c/82f132cb4418ee6d953d982b19191e87e2a6372c8a4ce36e50b69d6ade4a/cffi-2.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:716ff8ec22f20b4d988b12884086bcef0fc99737043e503f7a3935a6be99b1ea", size = 184746, upload-time = "2026-07-06T21:33:08.071Z" }, + { url = "https://files.pythonhosted.org/packages/a0/1c/4ed5a0e5bdca6cbc275556de3328dd1b76fd0c11cc13c88fe66d1d8715f2/cffi-2.1.0-cp313-cp313-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:63960549e4f8dc41e31accb97b975abaecfc44c03e396c093a6436763c2ea7db", size = 214747, upload-time = "2026-07-06T21:33:09.671Z" }, + { url = "https://files.pythonhosted.org/packages/3a/a6/e879bb68cc23a2bc9ba8f4b7d8019f0c2694bad2ab6c4a3701d429439f58/cffi-2.1.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ff067a8d8d880e7809e4ac88eb009bb848870115317b306666502ccad30b147f", size = 222392, upload-time = "2026-07-06T21:33:10.896Z" }, + { url = "https://files.pythonhosted.org/packages/88/f6/01890cfd63c08f8eb96a8319b0443690197d240a8bd6346048cf7bde9190/cffi-2.1.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:3b926723c13eba9f81d2ef3820d63aeceec3b2d4639906047bf675cb8a7a500d", size = 210285, upload-time = "2026-07-06T21:33:12.251Z" }, + { url = "https://files.pythonhosted.org/packages/a6/cf/2b684132056f438567b61e19d690dd31cd0921ace051e0a458be6074369e/cffi-2.1.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:47ff3a8bfd8cb9da1af7524b965127095055654c177fcfc7578debcb015eecd0", size = 208801, upload-time = "2026-07-06T21:33:13.617Z" }, + { url = "https://files.pythonhosted.org/packages/6f/08/f2e7d62c460faae0926f2d6e423694aa409ced3bc1fe2927a0a6e5f05416/cffi-2.1.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:799416bae98336e400981ff6e532d67d5c709cfb30afb79865a1315f94b0e224", size = 221808, upload-time = "2026-07-06T21:33:15.466Z" }, + { url = "https://files.pythonhosted.org/packages/38/37/04f54b8e63a02f3d908332c9effbf8c366167c6f733ed8a3d4f79b7e2a1e/cffi-2.1.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:961be50688f7fba2fa65f63712d3b9b341a22311f5253460ce933f52f0de1c8c", size = 225241, upload-time = "2026-07-06T21:33:16.869Z" }, + { url = "https://files.pythonhosted.org/packages/a9/d6/c72eecca433cd3e681c65ed313ab4835d9d4a379704d0f628a6a05f51c2e/cffi-2.1.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:bf5c6cf48238b0eb4c086978c492ad1cbc22373fc5b2d7353b3a598ce6db887a", size = 223588, upload-time = "2026-07-06T21:33:18.239Z" }, + { url = "https://files.pythonhosted.org/packages/c6/4b/e706f67279140f92939da3475ad610df18bfd52d50f14953a8e5fede71d5/cffi-2.1.0-cp313-cp313-win32.whl", hash = "sha256:db3eb7d46527159a878ec3460e9d40615bc25ba337d477db681aea6e4f05c5d2", size = 175248, upload-time = "2026-07-06T21:33:19.799Z" }, + { url = "https://files.pythonhosted.org/packages/5a/47/59eb7975cb0e4ef0afa764ea945b29a5bb4537a9f771cb7d6c8a5dd74c95/cffi-2.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:8e74a6135550c4748af665b1b1118b6aab33b1fc6a16f9aff630af107c3b4512", size = 185717, upload-time = "2026-07-06T21:33:21.47Z" }, + { url = "https://files.pythonhosted.org/packages/5a/af/34fee85c48f8d94efc8597bc09470c9dd274c145f1c12e0fbc6ab6d38d74/cffi-2.1.0-cp313-cp313-win_arm64.whl", hash = "sha256:2282cd5e38aa8accd03e99d1256af8411c84cdbee6a89d841b563fdbd1f3e50f", size = 180114, upload-time = "2026-07-06T21:33:22.515Z" }, + { url = "https://files.pythonhosted.org/packages/d8/f0/81478e482afa03f6d18dc8f2afb5edc45b3080853b634b5ed91961be0998/cffi-2.1.0-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:d2117334c3af3bdcb9a88522b844a2bdb5efdc4f71c6c822df55486ae1c3347a", size = 194142, upload-time = "2026-07-06T21:33:23.657Z" }, + { url = "https://files.pythonhosted.org/packages/7d/95/8de304305cd9204974b0ca051b86d307cafca13aa575a0ef1b44d92c0d8c/cffi-2.1.0-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:702c436735fbe99d59ada02a1f65cfc0d31c0ee8b7290912f8fbc5cd1e4b16c3", size = 196819, upload-time = "2026-07-06T21:33:25.007Z" }, + { url = "https://files.pythonhosted.org/packages/20/71/7c8372d30e42415602ed9f268f7cfd66f1b855fed881ecd168bcb45dbc0b/cffi-2.1.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:1ff3456eab0d889592d1936d6125bbfbc7ae4d3354a700f8bd80450a66445d4d", size = 184965, upload-time = "2026-07-06T21:33:26.605Z" }, + { url = "https://files.pythonhosted.org/packages/d6/5c/584e626835f0375c928176c04137c96927165cb8733cdb3150ec04e5ee5e/cffi-2.1.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c4165821e131d6d4ca444347c2b694e2311bcfa3fe5a861cc72968f28867beac", size = 184952, upload-time = "2026-07-06T21:33:27.823Z" }, + { url = "https://files.pythonhosted.org/packages/2e/d2/065fcae1c73979fac8e054462478d0ff8a29c40cdc2ed7ea5676a061df53/cffi-2.1.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:276f20fffd7b396e12516ba8edf9509210ac248cbbc5acbc39cd512f9f59ebe6", size = 222353, upload-time = "2026-07-06T21:33:29.178Z" }, + { url = "https://files.pythonhosted.org/packages/ed/a5/e8bbb1ce5b3ac2f53ad6a10bde44318a5a8d99d4f4a000d44a6e39aeb3e4/cffi-2.1.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:7d5980a3433d4b71a5e120f9dd551403d7824e31e2e67124fe2769c404c06913", size = 210051, upload-time = "2026-07-06T21:33:30.534Z" }, + { url = "https://files.pythonhosted.org/packages/28/ed/c127d3ac36e899c965e3361357c3befacd6578c03f40125183e41c3b219e/cffi-2.1.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:6ca4919c6e4f89aa99c42510b42cf54596892c00b3f9077f6bdd1505e24b9c8d", size = 208630, upload-time = "2026-07-06T21:33:31.753Z" }, + { url = "https://files.pythonhosted.org/packages/cc/d7/97d3136f81db489ec8d1d67748c110d6c994268fd7528014aa9f2b085e4e/cffi-2.1.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d53d10f7da99ae46f7373b9150393e9c5eab9b224909982b43832668de4779f5", size = 221593, upload-time = "2026-07-06T21:33:33.044Z" }, + { url = "https://files.pythonhosted.org/packages/d3/27/93195977168ee63aed233a1a0993a2178798654d1f4bddcdd321d6fd3b21/cffi-2.1.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c351efb95e832a853a29361675f33a7ce53de1a109cd73fd47af0712213aa4ce", size = 225146, upload-time = "2026-07-06T21:33:34.224Z" }, + { url = "https://files.pythonhosted.org/packages/b3/c1/6dbd291ee2ae5a50a034aa057207081f545923bbf15dad4511e985aafff5/cffi-2.1.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:dbf7c7a88e2bac086f06d14577332760bdeecc42bdec8ac4077f6260557d9326", size = 223240, upload-time = "2026-07-06T21:33:35.57Z" }, + { url = "https://files.pythonhosted.org/packages/0f/6f/ade5ce9863a57992a6ea3d0d10d7e29b8749fc127204b3d493d667b2815f/cffi-2.1.0-cp314-cp314-win32.whl", hash = "sha256:1854b724d00f6654c742097d5387569021be12d3a0f770eae1df8f8acfcc6acd", size = 177723, upload-time = "2026-07-06T21:33:51.626Z" }, + { url = "https://files.pythonhosted.org/packages/41/de/92b9eeed4ae4a21d6fd9b2a2c8505cbed573299902ea73981cc13f7ff62c/cffi-2.1.0-cp314-cp314-win_amd64.whl", hash = "sha256:1b96bfe2c4bd825681b7d311ad6d9b7280a091f43e8f63da5729638083cd3bfb", size = 187937, upload-time = "2026-07-06T21:33:53.403Z" }, + { url = "https://files.pythonhosted.org/packages/2e/1a/cc6ae6c2913a03aab8898eee57963cf1035b8df5872ed8b9115fcc7e2be8/cffi-2.1.0-cp314-cp314-win_arm64.whl", hash = "sha256:7d28dff1db6764108bc30788d85d61c876beff416d9a49cb9dd7c5a9f34f5804", size = 183001, upload-time = "2026-07-06T21:33:54.74Z" }, + { url = "https://files.pythonhosted.org/packages/14/f0/134c00ce0779ec86dea2aa1aac69339c2741a8045072676763512363a2ea/cffi-2.1.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:7ea6b3e2c4250ff1de21c630fe72d0f63eb95c2c32ffbf64a358cf4a8836d714", size = 188538, upload-time = "2026-07-06T21:33:36.792Z" }, + { url = "https://files.pythonhosted.org/packages/50/d8/3b86aba791cb610d24e8a3e1b2cd529e71fa15096b04e4d4e360049d4a4c/cffi-2.1.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:6af371f3767faeffc6ac1ef57cdfd25844403e9d3f476c5537caee499de96376", size = 188230, upload-time = "2026-07-06T21:33:38.011Z" }, + { url = "https://files.pythonhosted.org/packages/14/d0/117dcd9209255ad8571fbc8c92ef32593a1d294dcec91ddc4e4db50606f2/cffi-2.1.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:eb4e8997a49aa2c08a3e43c9045d224448b8941d88e7ac163c7d383e560cbf98", size = 223899, upload-time = "2026-07-06T21:33:39.514Z" }, + { url = "https://files.pythonhosted.org/packages/b6/3d/f20f8b886b254e3ad10e15cd4186d3aed49f3e6a35ab37aab9f8f25f7c03/cffi-2.1.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:bf01d8c84cbea96b944c73b22182e6c7c432b3475632b8111dbfdc95ddad6e13", size = 211652, upload-time = "2026-07-06T21:33:40.851Z" }, + { url = "https://files.pythonhosted.org/packages/28/3b/fad54de07260b93ddeef4b96d0131d57ea900675df1d410ae1deee52d7a6/cffi-2.1.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:33eb1ad83ebe8f313e0df035c406227d55a79456704a863fad9842136af5ad7d", size = 210755, upload-time = "2026-07-06T21:33:42.183Z" }, + { url = "https://files.pythonhosted.org/packages/cc/82/3d5c705acb7abbba9bbd7d79b8e62e0f25b6120eb7ae6ac49f1b721722fe/cffi-2.1.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ac0f1a2d0cfa7eea3f2aaf006ab6e70e8feeb16b75d65b7e5939982ca2f11056", size = 223933, upload-time = "2026-07-06T21:33:43.603Z" }, + { url = "https://files.pythonhosted.org/packages/6c/d0/47e338384ab6b1004241002fa616301020cea4fc95f283506565d252f276/cffi-2.1.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:c16914df9fb7f500e440e6875fa23ff5e0b31db01fa9c06af98d59a91f0dc2e4", size = 226749, upload-time = "2026-07-06T21:33:45.046Z" }, + { url = "https://files.pythonhosted.org/packages/70/25/65bd5b58ea4bfdfc15cde02cb5365f89ef8ab8b2adfb8fe5c4bd4233382f/cffi-2.1.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5ecbd0499275d57506d397eebe1981cee87b47fcd9ef5c22cab7ed7644a39a94", size = 225703, upload-time = "2026-07-06T21:33:46.374Z" }, + { url = "https://files.pythonhosted.org/packages/dc/78/aa01ac599a8a4322533d45a1f9bc93b338276d2d59dabbe7c6d92a775c81/cffi-2.1.0-cp314-cp314t-win32.whl", hash = "sha256:7d034dcffa09e9a46c93fa3a3be402096cb5354ac6e41ab8e5cc9cd8b642ad76", size = 182857, upload-time = "2026-07-06T21:33:47.696Z" }, + { url = "https://files.pythonhosted.org/packages/b9/26/d00496b22de4d4228f32dde94ad996f350c8aad676d63bcca0743c8dea4d/cffi-2.1.0-cp314-cp314t-win_amd64.whl", hash = "sha256:0582a58f3051372229ca8e7f5f589f9e5632678208d8636fea3676711fdf7fe5", size = 194065, upload-time = "2026-07-06T21:33:48.953Z" }, + { url = "https://files.pythonhosted.org/packages/d5/dd/0c7dbf815a579ff005008a2d815a55d6bb047c349eef536d9dc53d3f0a8d/cffi-2.1.0-cp314-cp314t-win_arm64.whl", hash = "sha256:510aeeeac94811b138077451da1fb18b308a5feab47dd2b603af55804155e1c8", size = 186404, upload-time = "2026-07-06T21:33:50.309Z" }, + { url = "https://files.pythonhosted.org/packages/55/c7/8c8c50cb11c6750051daf12164098a9a6f027ac4356967fd4d800a07f242/cffi-2.1.0-cp315-cp315-ios_13_0_arm64_iphoneos.whl", hash = "sha256:2e9dabb9abcb7ad15938c7196ad5c1718a4e6d33cc79b4c0209bdb64c4a54a5c", size = 194121, upload-time = "2026-07-06T21:33:56.109Z" }, + { url = "https://files.pythonhosted.org/packages/99/e2/67680bf19a6b60d2bb7ff83baefa2a4c3d2d7dc0f3277034b802e1fc504c/cffi-2.1.0-cp315-cp315-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:37f525a7e7e50c017fdebe58b787be310ad59357ae43a053943a6e1a6c526001", size = 196820, upload-time = "2026-07-06T21:33:57.288Z" }, + { url = "https://files.pythonhosted.org/packages/ed/da/4bbe583a3b3a5c8c60892124fe17f3fa3656523faf0d3484eae90f091853/cffi-2.1.0-cp315-cp315-macosx_10_15_x86_64.whl", hash = "sha256:95f2954c2c9473d892eca6e0409f3568b37ab62a8eedb122461f73cc273476e3", size = 184936, upload-time = "2026-07-06T21:33:58.765Z" }, + { url = "https://files.pythonhosted.org/packages/e5/4b/1f4c36ab273980d7aa75bb126ea4f8971f24a96108acad3a0a084028c57b/cffi-2.1.0-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:cdf2448aab5f661c9315308ec8b93f4e8a1a67a3c733f8631067a2b67d5913dc", size = 185045, upload-time = "2026-07-06T21:34:00.085Z" }, + { url = "https://files.pythonhosted.org/packages/ef/c3/ad299dc38f3583f8d916b299f028af418a9ec98bc695fcbebeae7420691c/cffi-2.1.0-cp315-cp315-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:90bec57cf82089383bd06a605b3eb8daebf7e5a668520beaf6e327a83a947699", size = 222342, upload-time = "2026-07-06T21:34:01.814Z" }, + { url = "https://files.pythonhosted.org/packages/eb/d8/df4543cc087245044ed02ef3ad8e0a26619d0075ac7a77a12dc81177851b/cffi-2.1.0-cp315-cp315-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:6274dcb2d15cef48daa73ed1be5a40d501d74dccd0cd6db364776d12cb6ba022", size = 210073, upload-time = "2026-07-06T21:34:03.255Z" }, + { url = "https://files.pythonhosted.org/packages/2c/0e/fac738d73728c6cea2a88a2883dca54892496cbba88a1dc1f2909cb8a6f5/cffi-2.1.0-cp315-cp315-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:2b71d409cccee78310ab5dec549aed052aaea483346e282c7b02362596e01bb0", size = 208551, upload-time = "2026-07-06T21:34:04.433Z" }, + { url = "https://files.pythonhosted.org/packages/e6/3f/0b04a700dd64f465c93020253a793a82c9b4dff9961f48facd0df945d9b8/cffi-2.1.0-cp315-cp315-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7d3538f9c0e50670f4deb93dbb696576e60590369cae2faf7de681e597a8a1f1", size = 221649, upload-time = "2026-07-06T21:34:06.157Z" }, + { url = "https://files.pythonhosted.org/packages/5d/7c/b7379a5704c79eda57ce075869ba70a0368d1c850f803b3c0d078d39dcaf/cffi-2.1.0-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:8f9ec95b8a043d3dfbc74d9abc6f7baf524dd27a8dc160b0a32ff9cdab650c28", size = 225203, upload-time = "2026-07-06T21:34:07.489Z" }, + { url = "https://files.pythonhosted.org/packages/5a/02/d5e6c43ea85c41bda2a184a3418f195fe7cf602967a8d2b94e085b83deef/cffi-2.1.0-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:af5e2915d41fe6c961694d7bfdc8562942638200f3ce2765dfb8b745cf997629", size = 223263, upload-time = "2026-07-06T21:34:08.712Z" }, + { url = "https://files.pythonhosted.org/packages/2c/d8/772b8259bf75749adffb1c546828978381fb516f60cf701f6c83daf60c85/cffi-2.1.0-cp315-cp315-win32.whl", hash = "sha256:0a42c688d19fca6e095a53c6a6e2295a5b050a8b289f109adab02a9e61a25de6", size = 177696, upload-time = "2026-07-06T21:34:26.355Z" }, + { url = "https://files.pythonhosted.org/packages/2f/dd/afa2191fc6d57fedd26e5844a2fe2fcc0bbfa00961bbaa5a41e4921e7cca/cffi-2.1.0-cp315-cp315-win_amd64.whl", hash = "sha256:bccbbb5ee76a61f9d99b5bf3846a51d7fca4b6a732fe46f89295610edaf41853", size = 187914, upload-time = "2026-07-06T21:34:27.58Z" }, + { url = "https://files.pythonhosted.org/packages/05/ef/6cd4f8c671517162379dc79cfae5aea9106bc38abb89628d5c16adf6a838/cffi-2.1.0-cp315-cp315-win_arm64.whl", hash = "sha256:8d35c139744adb3e727cd51b1a18324bbe44b8bd41bf8322bca4d41289f48eda", size = 183004, upload-time = "2026-07-06T21:34:28.905Z" }, + { url = "https://files.pythonhosted.org/packages/11/b6/12fc55092817a5faa26fb8c40c7f9d662e11a46ee248c137aafc42517d92/cffi-2.1.0-cp315-cp315t-macosx_10_15_x86_64.whl", hash = "sha256:f9912624a0c0b834b7520d7769b3644453aabc0a7e1c839da7359f050750e9bc", size = 188378, upload-time = "2026-07-06T21:34:09.926Z" }, + { url = "https://files.pythonhosted.org/packages/8d/2e/cdac88979f295fde5daa69622c7d2111e56e7ceb94f211357fbe452339e4/cffi-2.1.0-cp315-cp315t-macosx_11_0_arm64.whl", hash = "sha256:df92f2aba50eb4d96718b68ef76f2e57a57b54f2fa62333496d16c6d585a85ca", size = 188319, upload-time = "2026-07-06T21:34:11.101Z" }, + { url = "https://files.pythonhosted.org/packages/e0/27/1d0b408497e41a74795af122d7b603c418c5fed0171450f899afd04e594f/cffi-2.1.0-cp315-cp315t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:0520e1f4c35f44e209cbbb421b67eec42e6a157f59444dfb6058874ff3610e5d", size = 223904, upload-time = "2026-07-06T21:34:12.606Z" }, + { url = "https://files.pythonhosted.org/packages/8b/31/e115c985105dd7ffb32444505f18ceb874bb42d992af05d5dced7ecf1980/cffi-2.1.0-cp315-cp315t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:3681e031db29958a7502f5c0c9d6bbc4c36cb20f7b104086fa642d1799631ff8", size = 211554, upload-time = "2026-07-06T21:34:13.987Z" }, + { url = "https://files.pythonhosted.org/packages/5a/67/9e6e09409336d9e515c58367e7cfcf4f89df06ad25252675595a58eb59d5/cffi-2.1.0-cp315-cp315t-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:762f99479dcb369f60ab9017ad4ab97a36a1dd7c1ee5a3b15db0f4b8659120cd", size = 210795, upload-time = "2026-07-06T21:34:15.972Z" }, + { url = "https://files.pythonhosted.org/packages/19/e5/d3cc82a4a0be7902af279c04181ad038449c096734464a5ae1de3e1401bd/cffi-2.1.0-cp315-cp315t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:0611e7ebf90573a535ebdc33ae9da222d037853983e13359f580fab781ca017f", size = 223843, upload-time = "2026-07-06T21:34:17.509Z" }, + { url = "https://files.pythonhosted.org/packages/b9/65/b434abc97ce7cecc2c640fde160507c0ecc7e21544b483ba3325d2e2ea17/cffi-2.1.0-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:86cf8755a791f72c85dc287128cc62d4f24d392e3f1e15837245623f4a33cccc", size = 226773, upload-time = "2026-07-06T21:34:19.05Z" }, + { url = "https://files.pythonhosted.org/packages/b5/9f/d4dc66ca651eb1145a133314cda721abf13cfac3d28c4a0402263ae6ad75/cffi-2.1.0-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:ba00f661f8ba35d075c937174e27c2c421cec3942fd2e0ea3e66996757c0fdd9", size = 225719, upload-time = "2026-07-06T21:34:20.576Z" }, + { url = "https://files.pythonhosted.org/packages/68/5a/e536c528bc8057496c360c0978559a2dc45653f89dd6151078aa7d8fca1a/cffi-2.1.0-cp315-cp315t-win32.whl", hash = "sha256:cb96698e3c7413d906ce83f8ffd245ec1bd94707541f299d0ce4d6b0193e982b", size = 182760, upload-time = "2026-07-06T21:34:22.059Z" }, + { url = "https://files.pythonhosted.org/packages/d3/0b/0ffe8b82d3875bced5fa1e7986a7a46b748262a40ab7f60b475eb9fb1bb3/cffi-2.1.0-cp315-cp315t-win_amd64.whl", hash = "sha256:f146d154428a2523f9cc7936c02353c2459b8f6cf07d3cd1ee1c0a611109c5d5", size = 193769, upload-time = "2026-07-06T21:34:23.589Z" }, + { url = "https://files.pythonhosted.org/packages/a0/17/1073b53b68c9b5ca6914adf5f8bf55aacc2d3be102418c90700160ea8605/cffi-2.1.0-cp315-cp315t-win_arm64.whl", hash = "sha256:cbb7640ce37159548d2147b5b8c241f962143d4c71231431820783f4dc78f210", size = 186405, upload-time = "2026-07-06T21:34:24.857Z" }, ] [[package]] name = "cfgv" version = "3.5.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/4e/b5/721b8799b04bf9afe054a3899c6cf4e880fcf8563cc71c15610242490a0c/cfgv-3.5.0.tar.gz", hash = "sha256:d5b1034354820651caa73ede66a6294d6e95c1b00acc5e9b098e917404669132", size = 7334 } +sdist = { url = "https://files.pythonhosted.org/packages/4e/b5/721b8799b04bf9afe054a3899c6cf4e880fcf8563cc71c15610242490a0c/cfgv-3.5.0.tar.gz", hash = "sha256:d5b1034354820651caa73ede66a6294d6e95c1b00acc5e9b098e917404669132", size = 7334, upload-time = "2025-11-19T20:55:51.612Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/db/3c/33bac158f8ab7f89b2e59426d5fe2e4f63f7ed25df84c036890172b412b5/cfgv-3.5.0-py2.py3-none-any.whl", hash = "sha256:a8dc6b26ad22ff227d2634a65cb388215ce6cc96bbcc5cfde7641ae87e8dacc0", size = 7445 }, + { url = "https://files.pythonhosted.org/packages/db/3c/33bac158f8ab7f89b2e59426d5fe2e4f63f7ed25df84c036890172b412b5/cfgv-3.5.0-py2.py3-none-any.whl", hash = "sha256:a8dc6b26ad22ff227d2634a65cb388215ce6cc96bbcc5cfde7641ae87e8dacc0", size = 7445, upload-time = "2025-11-19T20:55:50.744Z" }, ] [[package]] name = "charset-normalizer" -version = "3.4.7" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e7/a1/67fe25fac3c7642725500a3f6cfe5821ad557c3abb11c9d20d12c7008d3e/charset_normalizer-3.4.7.tar.gz", hash = "sha256:ae89db9e5f98a11a4bf50407d4363e7b09b31e55bc117b4f7d80aab97ba009e5", size = 144271 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/c2/d7/b5b7020a0565c2e9fa8c09f4b5fa6232feb326b8c20081ccded47ea368fd/charset_normalizer-3.4.7-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:7641bb8895e77f921102f72833904dcd9901df5d6d72a2ab8f31d04b7e51e4e7", size = 309705 }, - { url = "https://files.pythonhosted.org/packages/5a/53/58c29116c340e5456724ecd2fff4196d236b98f3da97b404bc5e51ac3493/charset_normalizer-3.4.7-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:202389074300232baeb53ae2569a60901f7efadd4245cf3a3bf0617d60b439d7", size = 206419 }, - { url = "https://files.pythonhosted.org/packages/b2/02/e8146dc6591a37a00e5144c63f29fb7c97a734ea8a111190783c0e60ab63/charset_normalizer-3.4.7-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:30b8d1d8c52a48c2c5690e152c169b673487a2a58de1ec7393196753063fcd5e", size = 227901 }, - { url = "https://files.pythonhosted.org/packages/fb/73/77486c4cd58f1267bf17db420e930c9afa1b3be3fe8c8b8ebbebc9624359/charset_normalizer-3.4.7-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:532bc9bf33a68613fd7d65e4b1c71a6a38d7d42604ecf239c77392e9b4e8998c", size = 222742 }, - { url = "https://files.pythonhosted.org/packages/a1/fa/f74eb381a7d94ded44739e9d94de18dc5edc9c17fb8c11f0a6890696c0a9/charset_normalizer-3.4.7-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2fe249cb4651fd12605b7288b24751d8bfd46d35f12a20b1ba33dea122e690df", size = 214061 }, - { url = "https://files.pythonhosted.org/packages/dc/92/42bd3cefcf7687253fb86694b45f37b733c97f59af3724f356fa92b8c344/charset_normalizer-3.4.7-cp311-cp311-manylinux_2_31_armv7l.whl", hash = "sha256:65bcd23054beab4d166035cabbc868a09c1a49d1efe458fe8e4361215df40265", size = 199239 }, - { url = "https://files.pythonhosted.org/packages/4c/3d/069e7184e2aa3b3cddc700e3dd267413dc259854adc3380421c805c6a17d/charset_normalizer-3.4.7-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:08e721811161356f97b4059a9ba7bafb23ea5ee2255402c42881c214e173c6b4", size = 210173 }, - { url = "https://files.pythonhosted.org/packages/62/51/9d56feb5f2e7074c46f93e0ebdbe61f0848ee246e2f0d89f8e20b89ebb8f/charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:e060d01aec0a910bdccb8be71faf34e7799ce36950f8294c8bf612cba65a2c9e", size = 209841 }, - { url = "https://files.pythonhosted.org/packages/d2/59/893d8f99cc4c837dda1fe2f1139079703deb9f321aabcb032355de13b6c7/charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:38c0109396c4cfc574d502df99742a45c72c08eff0a36158b6f04000043dbf38", size = 200304 }, - { url = "https://files.pythonhosted.org/packages/7d/1d/ee6f3be3464247578d1ed5c46de545ccc3d3ff933695395c402c21fa6b77/charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:1c2a768fdd44ee4a9339a9b0b130049139b8ce3c01d2ce09f67f5a68048d477c", size = 229455 }, - { url = "https://files.pythonhosted.org/packages/54/bb/8fb0a946296ea96a488928bdce8ef99023998c48e4713af533e9bb98ef07/charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:1a87ca9d5df6fe460483d9a5bbf2b18f620cbed41b432e2bddb686228282d10b", size = 210036 }, - { url = "https://files.pythonhosted.org/packages/9a/bc/015b2387f913749f82afd4fcba07846d05b6d784dd16123cb66860e0237d/charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:d635aab80466bc95771bb78d5370e74d36d1fe31467b6b29b8b57b2a3cd7d22c", size = 224739 }, - { url = "https://files.pythonhosted.org/packages/17/ab/63133691f56baae417493cba6b7c641571a2130eb7bceba6773367ab9ec5/charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ae196f021b5e7c78e918242d217db021ed2a6ace2bc6ae94c0fc596221c7f58d", size = 216277 }, - { url = "https://files.pythonhosted.org/packages/06/6d/3be70e827977f20db77c12a97e6a9f973631a45b8d186c084527e53e77a4/charset_normalizer-3.4.7-cp311-cp311-win32.whl", hash = "sha256:adb2597b428735679446b46c8badf467b4ca5f5056aae4d51a19f9570301b1ad", size = 147819 }, - { url = "https://files.pythonhosted.org/packages/20/d9/5f67790f06b735d7c7637171bbfd89882ad67201891b7275e51116ed8207/charset_normalizer-3.4.7-cp311-cp311-win_amd64.whl", hash = "sha256:8e385e4267ab76874ae30db04c627faaaf0b509e1ccc11a95b3fc3e83f855c00", size = 159281 }, - { url = "https://files.pythonhosted.org/packages/ca/83/6413f36c5a34afead88ce6f66684d943d91f233d76dd083798f9602b75ae/charset_normalizer-3.4.7-cp311-cp311-win_arm64.whl", hash = "sha256:d4a48e5b3c2a489fae013b7589308a40146ee081f6f509e047e0e096084ceca1", size = 147843 }, - { url = "https://files.pythonhosted.org/packages/0c/eb/4fc8d0a7110eb5fc9cc161723a34a8a6c200ce3b4fbf681bc86feee22308/charset_normalizer-3.4.7-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:eca9705049ad3c7345d574e3510665cb2cf844c2f2dcfe675332677f081cbd46", size = 311328 }, - { url = "https://files.pythonhosted.org/packages/f8/e3/0fadc706008ac9d7b9b5be6dc767c05f9d3e5df51744ce4cc9605de7b9f4/charset_normalizer-3.4.7-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6178f72c5508bfc5fd446a5905e698c6212932f25bcdd4b47a757a50605a90e2", size = 208061 }, - { url = "https://files.pythonhosted.org/packages/42/f0/3dd1045c47f4a4604df85ec18ad093912ae1344ac706993aff91d38773a2/charset_normalizer-3.4.7-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e1421b502d83040e6d7fb2fb18dff63957f720da3d77b2fbd3187ceb63755d7b", size = 229031 }, - { url = "https://files.pythonhosted.org/packages/dc/67/675a46eb016118a2fbde5a277a5d15f4f69d5f3f5f338e5ee2f8948fcf43/charset_normalizer-3.4.7-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:edac0f1ab77644605be2cbba52e6b7f630731fc42b34cb0f634be1a6eface56a", size = 225239 }, - { url = "https://files.pythonhosted.org/packages/4b/f8/d0118a2f5f23b02cd166fa385c60f9b0d4f9194f574e2b31cef350ad7223/charset_normalizer-3.4.7-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5649fd1c7bade02f320a462fdefd0b4bd3ce036065836d4f42e0de958038e116", size = 216589 }, - { url = "https://files.pythonhosted.org/packages/b1/f1/6d2b0b261b6c4ceef0fcb0d17a01cc5bc53586c2d4796fa04b5c540bc13d/charset_normalizer-3.4.7-cp312-cp312-manylinux_2_31_armv7l.whl", hash = "sha256:203104ed3e428044fd943bc4bf45fa73c0730391f9621e37fe39ecf477b128cb", size = 202733 }, - { url = "https://files.pythonhosted.org/packages/6f/c0/7b1f943f7e87cc3db9626ba17807d042c38645f0a1d4415c7a14afb5591f/charset_normalizer-3.4.7-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:298930cec56029e05497a76988377cbd7457ba864beeea92ad7e844fe74cd1f1", size = 212652 }, - { url = "https://files.pythonhosted.org/packages/38/dd/5a9ab159fe45c6e72079398f277b7d2b523e7f716acc489726115a910097/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:708838739abf24b2ceb208d0e22403dd018faeef86ddac04319a62ae884c4f15", size = 211229 }, - { url = "https://files.pythonhosted.org/packages/d5/ff/531a1cad5ca855d1c1a8b69cb71abfd6d85c0291580146fda7c82857caa1/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:0f7eb884681e3938906ed0434f20c63046eacd0111c4ba96f27b76084cd679f5", size = 203552 }, - { url = "https://files.pythonhosted.org/packages/c1/4c/a5fb52d528a8ca41f7598cb619409ece30a169fbdf9cdce592e53b46c3a6/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:4dc1e73c36828f982bfe79fadf5919923f8a6f4df2860804db9a98c48824ce8d", size = 230806 }, - { url = "https://files.pythonhosted.org/packages/59/7a/071feed8124111a32b316b33ae4de83d36923039ef8cf48120266844285b/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:aed52fea0513bac0ccde438c188c8a471c4e0f457c2dd20cdbf6ea7a450046c7", size = 212316 }, - { url = "https://files.pythonhosted.org/packages/fd/35/f7dba3994312d7ba508e041eaac39a36b120f32d4c8662b8814dab876431/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:fea24543955a6a729c45a73fe90e08c743f0b3334bbf3201e6c4bc1b0c7fa464", size = 227274 }, - { url = "https://files.pythonhosted.org/packages/8a/2d/a572df5c9204ab7688ec1edc895a73ebded3b023bb07364710b05dd1c9be/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:bb6d88045545b26da47aa879dd4a89a71d1dce0f0e549b1abcb31dfe4a8eac49", size = 218468 }, - { url = "https://files.pythonhosted.org/packages/86/eb/890922a8b03a568ca2f336c36585a4713c55d4d67bf0f0c78924be6315ca/charset_normalizer-3.4.7-cp312-cp312-win32.whl", hash = "sha256:2257141f39fe65a3fdf38aeccae4b953e5f3b3324f4ff0daf9f15b8518666a2c", size = 148460 }, - { url = "https://files.pythonhosted.org/packages/35/d9/0e7dffa06c5ab081f75b1b786f0aefc88365825dfcd0ac544bdb7b2b6853/charset_normalizer-3.4.7-cp312-cp312-win_amd64.whl", hash = "sha256:5ed6ab538499c8644b8a3e18debabcd7ce684f3fa91cf867521a7a0279cab2d6", size = 159330 }, - { url = "https://files.pythonhosted.org/packages/9e/5d/481bcc2a7c88ea6b0878c299547843b2521ccbc40980cb406267088bc701/charset_normalizer-3.4.7-cp312-cp312-win_arm64.whl", hash = "sha256:56be790f86bfb2c98fb742ce566dfb4816e5a83384616ab59c49e0604d49c51d", size = 147828 }, - { url = "https://files.pythonhosted.org/packages/c1/3b/66777e39d3ae1ddc77ee606be4ec6d8cbd4c801f65e5a1b6f2b11b8346dd/charset_normalizer-3.4.7-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:f496c9c3cc02230093d8330875c4c3cdfc3b73612a5fd921c65d39cbcef08063", size = 309627 }, - { url = "https://files.pythonhosted.org/packages/2e/4e/b7f84e617b4854ade48a1b7915c8ccfadeba444d2a18c291f696e37f0d3b/charset_normalizer-3.4.7-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0ea948db76d31190bf08bd371623927ee1339d5f2a0b4b1b4a4439a65298703c", size = 207008 }, - { url = "https://files.pythonhosted.org/packages/c4/bb/ec73c0257c9e11b268f018f068f5d00aa0ef8c8b09f7753ebd5f2880e248/charset_normalizer-3.4.7-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a277ab8928b9f299723bc1a2dabb1265911b1a76341f90a510368ca44ad9ab66", size = 228303 }, - { url = "https://files.pythonhosted.org/packages/85/fb/32d1f5033484494619f701e719429c69b766bfc4dbc61aa9e9c8c166528b/charset_normalizer-3.4.7-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3bec022aec2c514d9cf199522a802bd007cd588ab17ab2525f20f9c34d067c18", size = 224282 }, - { url = "https://files.pythonhosted.org/packages/fa/07/330e3a0dda4c404d6da83b327270906e9654a24f6c546dc886a0eb0ffb23/charset_normalizer-3.4.7-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e044c39e41b92c845bc815e5ae4230804e8e7bc29e399b0437d64222d92809dd", size = 215595 }, - { url = "https://files.pythonhosted.org/packages/e3/7c/fc890655786e423f02556e0216d4b8c6bcb6bdfa890160dc66bf52dee468/charset_normalizer-3.4.7-cp313-cp313-manylinux_2_31_armv7l.whl", hash = "sha256:f495a1652cf3fbab2eb0639776dad966c2fb874d79d87ca07f9d5f059b8bd215", size = 201986 }, - { url = "https://files.pythonhosted.org/packages/d8/97/bfb18b3db2aed3b90cf54dc292ad79fdd5ad65c4eae454099475cbeadd0d/charset_normalizer-3.4.7-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e712b419df8ba5e42b226c510472b37bd57b38e897d3eca5e8cfd410a29fa859", size = 211711 }, - { url = "https://files.pythonhosted.org/packages/6f/a5/a581c13798546a7fd557c82614a5c65a13df2157e9ad6373166d2a3e645d/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:7804338df6fcc08105c7745f1502ba68d900f45fd770d5bdd5288ddccb8a42d8", size = 210036 }, - { url = "https://files.pythonhosted.org/packages/8c/bf/b3ab5bcb478e4193d517644b0fb2bf5497fbceeaa7a1bc0f4d5b50953861/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:481551899c856c704d58119b5025793fa6730adda3571971af568f66d2424bb5", size = 202998 }, - { url = "https://files.pythonhosted.org/packages/e7/4e/23efd79b65d314fa320ec6017b4b5834d5c12a58ba4610aa353af2e2f577/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:f59099f9b66f0d7145115e6f80dd8b1d847176df89b234a5a6b3f00437aa0832", size = 230056 }, - { url = "https://files.pythonhosted.org/packages/b9/9f/1e1941bc3f0e01df116e68dc37a55c4d249df5e6fa77f008841aef68264f/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:f59ad4c0e8f6bba240a9bb85504faa1ab438237199d4cce5f622761507b8f6a6", size = 211537 }, - { url = "https://files.pythonhosted.org/packages/80/0f/088cbb3020d44428964a6c97fe1edfb1b9550396bf6d278330281e8b709c/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:3dedcc22d73ec993f42055eff4fcfed9318d1eeb9a6606c55892a26964964e48", size = 226176 }, - { url = "https://files.pythonhosted.org/packages/6a/9f/130394f9bbe06f4f63e22641d32fc9b202b7e251c9aef4db044324dac493/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:64f02c6841d7d83f832cd97ccf8eb8a906d06eb95d5276069175c696b024b60a", size = 217723 }, - { url = "https://files.pythonhosted.org/packages/73/55/c469897448a06e49f8fa03f6caae97074fde823f432a98f979cc42b90e69/charset_normalizer-3.4.7-cp313-cp313-win32.whl", hash = "sha256:4042d5c8f957e15221d423ba781e85d553722fc4113f523f2feb7b188cc34c5e", size = 148085 }, - { url = "https://files.pythonhosted.org/packages/5d/78/1b74c5bbb3f99b77a1715c91b3e0b5bdb6fe302d95ace4f5b1bec37b0167/charset_normalizer-3.4.7-cp313-cp313-win_amd64.whl", hash = "sha256:3946fa46a0cf3e4c8cb1cc52f56bb536310d34f25f01ca9b6c16afa767dab110", size = 158819 }, - { url = "https://files.pythonhosted.org/packages/68/86/46bd42279d323deb8687c4a5a811fd548cb7d1de10cf6535d099877a9a9f/charset_normalizer-3.4.7-cp313-cp313-win_arm64.whl", hash = "sha256:80d04837f55fc81da168b98de4f4b797ef007fc8a79ab71c6ec9bc4dd662b15b", size = 147915 }, - { url = "https://files.pythonhosted.org/packages/97/c8/c67cb8c70e19ef1960b97b22ed2a1567711de46c4ddf19799923adc836c2/charset_normalizer-3.4.7-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:c36c333c39be2dbca264d7803333c896ab8fa7d4d6f0ab7edb7dfd7aea6e98c0", size = 309234 }, - { url = "https://files.pythonhosted.org/packages/99/85/c091fdee33f20de70d6c8b522743b6f831a2f1cd3ff86de4c6a827c48a76/charset_normalizer-3.4.7-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1c2aed2e5e41f24ea8ef1590b8e848a79b56f3a5564a65ceec43c9d692dc7d8a", size = 208042 }, - { url = "https://files.pythonhosted.org/packages/87/1c/ab2ce611b984d2fd5d86a5a8a19c1ae26acac6bad967da4967562c75114d/charset_normalizer-3.4.7-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:54523e136b8948060c0fa0bc7b1b50c32c186f2fceee897a495406bb6e311d2b", size = 228706 }, - { url = "https://files.pythonhosted.org/packages/a8/29/2b1d2cb00bf085f59d29eb773ce58ec2d325430f8c216804a0a5cd83cbca/charset_normalizer-3.4.7-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:715479b9a2802ecac752a3b0efa2b0b60285cf962ee38414211abdfccc233b41", size = 224727 }, - { url = "https://files.pythonhosted.org/packages/47/5c/032c2d5a07fe4d4855fea851209cca2b6f03ebeb6d4e3afdb3358386a684/charset_normalizer-3.4.7-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bd6c2a1c7573c64738d716488d2cdd3c00e340e4835707d8fdb8dc1a66ef164e", size = 215882 }, - { url = "https://files.pythonhosted.org/packages/2c/c2/356065d5a8b78ed04499cae5f339f091946a6a74f91e03476c33f0ab7100/charset_normalizer-3.4.7-cp314-cp314-manylinux_2_31_armv7l.whl", hash = "sha256:c45e9440fb78f8ddabcf714b68f936737a121355bf59f3907f4e17721b9d1aae", size = 200860 }, - { url = "https://files.pythonhosted.org/packages/0c/cd/a32a84217ced5039f53b29f460962abb2d4420def55afabe45b1c3c7483d/charset_normalizer-3.4.7-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3534e7dcbdcf757da6b85a0bbf5b6868786d5982dd959b065e65481644817a18", size = 211564 }, - { url = "https://files.pythonhosted.org/packages/44/86/58e6f13ce26cc3b8f4a36b94a0f22ae2f00a72534520f4ae6857c4b81f89/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:e8ac484bf18ce6975760921bb6148041faa8fef0547200386ea0b52b5d27bf7b", size = 211276 }, - { url = "https://files.pythonhosted.org/packages/8f/fe/d17c32dc72e17e155e06883efa84514ca375f8a528ba2546bee73fc4df81/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:a5fe03b42827c13cdccd08e6c0247b6a6d4b5e3cdc53fd1749f5896adcdc2356", size = 201238 }, - { url = "https://files.pythonhosted.org/packages/6a/29/f33daa50b06525a237451cdb6c69da366c381a3dadcd833fa5676bc468b3/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:2d6eb928e13016cea4f1f21d1e10c1cebd5a421bc57ddf5b1142ae3f86824fab", size = 230189 }, - { url = "https://files.pythonhosted.org/packages/b6/6e/52c84015394a6a0bdcd435210a7e944c5f94ea1055f5cc5d56c5fe368e7b/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:e74327fb75de8986940def6e8dee4f127cc9752bee7355bb323cc5b2659b6d46", size = 211352 }, - { url = "https://files.pythonhosted.org/packages/8c/d7/4353be581b373033fb9198bf1da3cf8f09c1082561e8e922aa7b39bf9fe8/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:d6038d37043bced98a66e68d3aa2b6a35505dc01328cd65217cefe82f25def44", size = 227024 }, - { url = "https://files.pythonhosted.org/packages/30/45/99d18aa925bd1740098ccd3060e238e21115fffbfdcb8f3ece837d0ace6c/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:7579e913a5339fb8fa133f6bbcfd8e6749696206cf05acdbdca71a1b436d8e72", size = 217869 }, - { url = "https://files.pythonhosted.org/packages/5c/05/5ee478aa53f4bb7996482153d4bfe1b89e0f087f0ab6b294fcf92d595873/charset_normalizer-3.4.7-cp314-cp314-win32.whl", hash = "sha256:5b77459df20e08151cd6f8b9ef8ef1f961ef73d85c21a555c7eed5b79410ec10", size = 148541 }, - { url = "https://files.pythonhosted.org/packages/48/77/72dcb0921b2ce86420b2d79d454c7022bf5be40202a2a07906b9f2a35c97/charset_normalizer-3.4.7-cp314-cp314-win_amd64.whl", hash = "sha256:92a0a01ead5e668468e952e4238cccd7c537364eb7d851ab144ab6627dbbe12f", size = 159634 }, - { url = "https://files.pythonhosted.org/packages/c6/a3/c2369911cd72f02386e4e340770f6e158c7980267da16af8f668217abaa0/charset_normalizer-3.4.7-cp314-cp314-win_arm64.whl", hash = "sha256:67f6279d125ca0046a7fd386d01b311c6363844deac3e5b069b514ba3e63c246", size = 148384 }, - { url = "https://files.pythonhosted.org/packages/94/09/7e8a7f73d24dba1f0035fbbf014d2c36828fc1bf9c88f84093e57d315935/charset_normalizer-3.4.7-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:effc3f449787117233702311a1b7d8f59cba9ced946ba727bdc329ec69028e24", size = 330133 }, - { url = "https://files.pythonhosted.org/packages/8d/da/96975ddb11f8e977f706f45cddd8540fd8242f71ecdb5d18a80723dcf62c/charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fbccdc05410c9ee21bbf16a35f4c1d16123dcdeb8a1d38f33654fa21d0234f79", size = 216257 }, - { url = "https://files.pythonhosted.org/packages/e5/e8/1d63bf8ef2d388e95c64b2098f45f84758f6d102a087552da1485912637b/charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:733784b6d6def852c814bce5f318d25da2ee65dd4839a0718641c696e09a2960", size = 234851 }, - { url = "https://files.pythonhosted.org/packages/9b/40/e5ff04233e70da2681fa43969ad6f66ca5611d7e669be0246c4c7aaf6dc8/charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a89c23ef8d2c6b27fd200a42aa4ac72786e7c60d40efdc76e6011260b6e949c4", size = 233393 }, - { url = "https://files.pythonhosted.org/packages/be/c1/06c6c49d5a5450f76899992f1ee40b41d076aee9279b49cf9974d2f313d5/charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6c114670c45346afedc0d947faf3c7f701051d2518b943679c8ff88befe14f8e", size = 223251 }, - { url = "https://files.pythonhosted.org/packages/2b/9f/f2ff16fb050946169e3e1f82134d107e5d4ae72647ec8a1b1446c148480f/charset_normalizer-3.4.7-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:a180c5e59792af262bf263b21a3c49353f25945d8d9f70628e73de370d55e1e1", size = 206609 }, - { url = "https://files.pythonhosted.org/packages/69/d5/a527c0cd8d64d2eab7459784fb4169a0ac76e5a6fc5237337982fd61347e/charset_normalizer-3.4.7-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3c9a494bc5ec77d43cea229c4f6db1e4d8fe7e1bbffa8b6f0f0032430ff8ab44", size = 220014 }, - { url = "https://files.pythonhosted.org/packages/7e/80/8a7b8104a3e203074dc9aa2c613d4b726c0e136bad1cc734594b02867972/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8d828b6667a32a728a1ad1d93957cdf37489c57b97ae6c4de2860fa749b8fc1e", size = 218979 }, - { url = "https://files.pythonhosted.org/packages/02/9a/b759b503d507f375b2b5c153e4d2ee0a75aa215b7f2489cf314f4541f2c0/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:cf1493cd8607bec4d8a7b9b004e699fcf8f9103a9284cc94962cb73d20f9d4a3", size = 209238 }, - { url = "https://files.pythonhosted.org/packages/c2/4e/0f3f5d47b86bdb79256e7290b26ac847a2832d9a4033f7eb2cd4bcf4bb5b/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:0c96c3b819b5c3e9e165495db84d41914d6894d55181d2d108cc1a69bfc9cce0", size = 236110 }, - { url = "https://files.pythonhosted.org/packages/96/23/bce28734eb3ed2c91dcf93abeb8a5cf393a7b2749725030bb630e554fdd8/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:752a45dc4a6934060b3b0dab47e04edc3326575f82be64bc4fc293914566503e", size = 219824 }, - { url = "https://files.pythonhosted.org/packages/2c/6f/6e897c6984cc4d41af319b077f2f600fc8214eb2fe2d6bcb79141b882400/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:8778f0c7a52e56f75d12dae53ae320fae900a8b9b4164b981b9c5ce059cd1fcb", size = 233103 }, - { url = "https://files.pythonhosted.org/packages/76/22/ef7bd0fe480a0ae9b656189ec00744b60933f68b4f42a7bb06589f6f576a/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ce3412fbe1e31eb81ea42f4169ed94861c56e643189e1e75f0041f3fe7020abe", size = 225194 }, - { url = "https://files.pythonhosted.org/packages/c5/a7/0e0ab3e0b5bc1219bd80a6a0d4d72ca74d9250cb2382b7c699c147e06017/charset_normalizer-3.4.7-cp314-cp314t-win32.whl", hash = "sha256:c03a41a8784091e67a39648f70c5f97b5b6a37f216896d44d2cdcb82615339a0", size = 159827 }, - { url = "https://files.pythonhosted.org/packages/7a/1d/29d32e0fb40864b1f878c7f5a0b343ae676c6e2b271a2d55cc3a152391da/charset_normalizer-3.4.7-cp314-cp314t-win_amd64.whl", hash = "sha256:03853ed82eeebbce3c2abfdbc98c96dc205f32a79627688ac9a27370ea61a49c", size = 174168 }, - { url = "https://files.pythonhosted.org/packages/de/32/d92444ad05c7a6e41fb2036749777c163baf7a0301a040cb672d6b2b1ae9/charset_normalizer-3.4.7-cp314-cp314t-win_arm64.whl", hash = "sha256:c35abb8bfff0185efac5878da64c45dafd2b37fb0383add1be155a763c1f083d", size = 153018 }, - { url = "https://files.pythonhosted.org/packages/db/8f/61959034484a4a7c527811f4721e75d02d653a35afb0b6054474d8185d4c/charset_normalizer-3.4.7-py3-none-any.whl", hash = "sha256:3dce51d0f5e7951f8bb4900c257dad282f49190fdbebecd4ba99bcc41fef404d", size = 61958 }, -] - -[[package]] -name = "click" -version = "8.3.2" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "colorama", marker = "sys_platform == 'win32'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/57/75/31212c6bf2503fdf920d87fee5d7a86a2e3bcf444984126f13d8e4016804/click-8.3.2.tar.gz", hash = "sha256:14162b8b3b3550a7d479eafa77dfd3c38d9dc8951f6f69c78913a8f9a7540fd5", size = 302856 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/e4/20/71885d8b97d4f3dde17b1fdb92dbd4908b00541c5a3379787137285f602e/click-8.3.2-py3-none-any.whl", hash = "sha256:1924d2c27c5653561cd2cae4548d1406039cb79b858b747cfea24924bbc1616d", size = 108379 }, +version = "3.4.9" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/bd/2a/23f34ec9d04624958e137efdc394888716353190e75f25dd22c7a2c7a8aa/charset_normalizer-3.4.9.tar.gz", hash = "sha256:673611bbd43f0810bec0b0f028ddeaaa501190339cac411f347ac76917c3ae7b", size = 152439, upload-time = "2026-07-07T14:34:58.454Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0b/e3/85ec501f206fb049259288c1f3506e53876937fb00edb47009348e66756b/charset_normalizer-3.4.9-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:0e94703ec9684807f20cfb5eed95c70f67f2a8f21ad620146d7b5a13677b93e5", size = 317075, upload-time = "2026-07-07T14:32:56.021Z" }, + { url = "https://files.pythonhosted.org/packages/c3/69/2a5385192e67175f7d8bd5ce4f57c24bc956439adeae5c13a99aa28a53d1/charset_normalizer-3.4.9-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2a441ea71902098ffe78c5abe6c494f44160b4af614ed16c3d9a3b1d17fd8ee2", size = 213837, upload-time = "2026-07-07T14:32:57.78Z" }, + { url = "https://files.pythonhosted.org/packages/b3/46/03ddc7da576d814fe0a36dd1f0fd3258e95404b4b2e3c026b7923d7e133f/charset_normalizer-3.4.9-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:304b13570067b2547562e308af560b3963857b1fa90bd6afd978130130fe2d6a", size = 235503, upload-time = "2026-07-07T14:32:59.205Z" }, + { url = "https://files.pythonhosted.org/packages/4e/6e/de0229a7ef40f6f9d28a837eebf4ec47bdca5dab4e900c84f22919af636a/charset_normalizer-3.4.9-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:4773092f8019072343a7447203308b176e10199920eb02d6195e81bbb3274c29", size = 229944, upload-time = "2026-07-07T14:33:00.803Z" }, + { url = "https://files.pythonhosted.org/packages/a5/34/49b9060e8418b14fb5cba9cf6bfb383111e2538a03a1fb18e66a95aeb3d5/charset_normalizer-3.4.9-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:04ce310cb89c15df659582aee80a0603788732a5e017d5bd5c81158106ce249c", size = 221276, upload-time = "2026-07-07T14:33:02.199Z" }, + { url = "https://files.pythonhosted.org/packages/44/95/80282cce0fae9c3061203d723ee87da996aed79679e65d8935050ee7ca1f/charset_normalizer-3.4.9-cp311-cp311-manylinux_2_31_armv7l.whl", hash = "sha256:c0323c9daef75ef2e5083624b4585018a0c9d5e3b40f607eed81a311270b934b", size = 205260, upload-time = "2026-07-07T14:33:03.698Z" }, + { url = "https://files.pythonhosted.org/packages/0c/74/2f62c8821b969ea3bd67cc2e6976834f48ca5d12664d2559ebcd9bcfbed7/charset_normalizer-3.4.9-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:871ff67ea1aad4dfd91736464934d56b32dac49f9fbe16cddba36198a7b3a0db", size = 217786, upload-time = "2026-07-07T14:33:05.12Z" }, + { url = "https://files.pythonhosted.org/packages/d9/8d/feabb82cb49fcad14515b1d7d1ca4787b0da7fc723a212bf89bc9e0fac52/charset_normalizer-3.4.9-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:67830fc78e67501f47bb950471b2dcb9b35b140084429318e862895a8e89c993", size = 216798, upload-time = "2026-07-07T14:33:06.629Z" }, + { url = "https://files.pythonhosted.org/packages/a5/ff/c946d63bc3786d5b84d960b0f7ab7e25b828486a946b5aa997625bcaf6a6/charset_normalizer-3.4.9-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:3d92613ec25e43b05f042302531ec0f00b8445190e43325880cbd6ab7c2581da", size = 206429, upload-time = "2026-07-07T14:33:08.006Z" }, + { url = "https://files.pythonhosted.org/packages/af/ba/5e5007c370702f85d2ef75791fac7943ed41e080364a673b20142e430e3e/charset_normalizer-3.4.9-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:280081916dc341820640489a66e4696049401ef1cf6dd672f672e70ad915aca3", size = 223066, upload-time = "2026-07-07T14:33:09.783Z" }, + { url = "https://files.pythonhosted.org/packages/83/d5/9096aa3cf532dfad237861544eb47a0f20d5adbf1039760fed8eaae935d9/charset_normalizer-3.4.9-cp311-cp311-win32.whl", hash = "sha256:ac351b3b8014eead140e77e9717e2992c6bbe30b63bc3422422eb84865412e3d", size = 150456, upload-time = "2026-07-07T14:33:11.217Z" }, + { url = "https://files.pythonhosted.org/packages/ed/a1/e29995109e455dc8eff8d0fac6ae509be39561318a7cfeac5d33ad029213/charset_normalizer-3.4.9-cp311-cp311-win_amd64.whl", hash = "sha256:6366a16e1a25018694d6a5d784d09b046edc9eac40ea2b54065c3052672516a1", size = 161410, upload-time = "2026-07-07T14:33:12.743Z" }, + { url = "https://files.pythonhosted.org/packages/4f/8d/1569f4d0032d6ba2a4fe4591c35bf87868c600c41a71eb5c2e1ffa8464c2/charset_normalizer-3.4.9-cp311-cp311-win_arm64.whl", hash = "sha256:1d22856ffbe153a602df38e4a5464f0b748a54002e0d69ac6d2ad0a197cc99ec", size = 152649, upload-time = "2026-07-07T14:33:14.173Z" }, + { url = "https://files.pythonhosted.org/packages/70/4a/ecbd131485c07fcdfad54e28946d513e3da22ef3b4bd854dcafae54ec739/charset_normalizer-3.4.9-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:45b0cc4e3556cd875e09102988d1ab8356c998b596c9fced84547c8138b487a0", size = 319300, upload-time = "2026-07-07T14:33:15.666Z" }, + { url = "https://files.pythonhosted.org/packages/ec/96/5d9364e3342d69f3a045e1777bc47c85c383e6e9466d561b33fdb419d1f9/charset_normalizer-3.4.9-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9b2aff1c7b3884512b9512c3eaadd9bab39fb45042ffaaa1dd08ff2b9f8109d9", size = 215802, upload-time = "2026-07-07T14:33:17.031Z" }, + { url = "https://files.pythonhosted.org/packages/4b/4c/5361f9aa7f2cb58d94f2ab831b3d493f69efb1d239654b4744e3c09527cb/charset_normalizer-3.4.9-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:9104ed0bd76a429d46f9ec0dbc9b08ad1d2dcdf2b00a5a0daa1c145329b35b44", size = 237171, upload-time = "2026-07-07T14:33:18.576Z" }, + { url = "https://files.pythonhosted.org/packages/50/78/ce342ca4ff30b2eb49fe6d9578df85974f90c67d294113e94efdd9664cbd/charset_normalizer-3.4.9-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:7b86a2b16095d250c6f58b3d9b2eee6f4147754344f3dab0922f7c9bf7d226c9", size = 233075, upload-time = "2026-07-07T14:33:20.084Z" }, + { url = "https://files.pythonhosted.org/packages/01/c4/4fa4c8b3097a11f3c5f09a35b72ed6855fb1d332469504962ab7bafcc702/charset_normalizer-3.4.9-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5e226f6218febc71f6c1fc2fafb91c226f75bdc1d8fb12d66823716e891608fd", size = 224256, upload-time = "2026-07-07T14:33:21.747Z" }, + { url = "https://files.pythonhosted.org/packages/87/3a/ad914516df7e358a81aae018caa5e0470ba827fa6d763b1d2e87d920a5f6/charset_normalizer-3.4.9-cp312-cp312-manylinux_2_31_armv7l.whl", hash = "sha256:90c44bc373b7687f6948b693cceaea1348ae0975d7474746559494468e3c1d84", size = 208784, upload-time = "2026-07-07T14:33:23.313Z" }, + { url = "https://files.pythonhosted.org/packages/d7/74/3c12f9755717dfe5c5c87da63f35d765fa0c00382ec26bf23f7fae34f2ba/charset_normalizer-3.4.9-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:9cdef90ae47919cae358d8ab15797a800ed41da7aba5d72419fb510729e2ed4b", size = 219928, upload-time = "2026-07-07T14:33:24.814Z" }, + { url = "https://files.pythonhosted.org/packages/33/9a/895095b83e7907abd6d3d99aad3a38ad0d9686cc186cb0c94c24320fe63e/charset_normalizer-3.4.9-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:60f44ade2cf573dad7a277e6f8ca9a51a21dda572b13bd7d8539bb3cd5dbedde", size = 218489, upload-time = "2026-07-07T14:33:26.42Z" }, + { url = "https://files.pythonhosted.org/packages/a1/34/ef5c05f412f42520d7709b7d3784d19640839eb7366ded1755511585429f/charset_normalizer-3.4.9-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:a1786910334ed46ab1dd73222f2cd1e05c2c3bb39f6dddb4f8b36fc382058a39", size = 210267, upload-time = "2026-07-07T14:33:27.952Z" }, + { url = "https://files.pythonhosted.org/packages/83/dc/9b29fa4412b318bf3bfea985c35d67eb55e04b59a7c3f2237168b0e0be6f/charset_normalizer-3.4.9-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:03d07803992c6c7bbc976327f34b18b6160327fc81cb82c9d504720ac0be3b62", size = 226030, upload-time = "2026-07-07T14:33:29.397Z" }, + { url = "https://files.pythonhosted.org/packages/0e/42/6dbc00b8cd16011691203e33570fa42ed5746599a2e878112d16eab403a3/charset_normalizer-3.4.9-cp312-cp312-win32.whl", hash = "sha256:78841cccf1af7b40f6f716338d50c0902dbe88d9f800b3c973b7a9a0a693a642", size = 151185, upload-time = "2026-07-07T14:33:30.781Z" }, + { url = "https://files.pythonhosted.org/packages/80/cc/f920afd1a23c58ccd53c1d36085a71893a4737ff5e66e0371efab6809850/charset_normalizer-3.4.9-cp312-cp312-win_amd64.whl", hash = "sha256:4b3dac63058cc36820b0dd072f89898604e2d39686fe05321729d00d8ac185a0", size = 162557, upload-time = "2026-07-07T14:33:32.176Z" }, + { url = "https://files.pythonhosted.org/packages/f0/e6/0386d43a261ff4e4b30c5857af7df877254b46bec7b9d1b74b6bf969a90b/charset_normalizer-3.4.9-cp312-cp312-win_arm64.whl", hash = "sha256:78fa18e436a1a0e58dbd7e02fc4473f3f32cceb12df9dfca542d075961c307d2", size = 152665, upload-time = "2026-07-07T14:33:33.711Z" }, + { url = "https://files.pythonhosted.org/packages/b2/06/97ec2aeae780b31d742b6352218b43841a6871e2564578ca522dce4a45c3/charset_normalizer-3.4.9-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:440eede837960000d74978f0eba527be106b5b9aee0daf779d395276ed0b0614", size = 317688, upload-time = "2026-07-07T14:33:35.408Z" }, + { url = "https://files.pythonhosted.org/packages/d0/39/8ff066c672434225f8d25f8b739f992af250944392173dcc88362681c9bf/charset_normalizer-3.4.9-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:21e764fd1e70b6a3e205a0e46f3051701f98a8cb3fad66eeb80e48bb502f8698", size = 214982, upload-time = "2026-07-07T14:33:36.996Z" }, + { url = "https://files.pythonhosted.org/packages/92/8f/3a47a3667c83c2df9483d91644c6c107de3bf8874aa1793da9d3012eb986/charset_normalizer-3.4.9-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e4fd89cc178bced6ad29cb3e6dd4aa63fa5017c3524dbd0b25998fb64a87cc8b", size = 236460, upload-time = "2026-07-07T14:33:38.536Z" }, + { url = "https://files.pythonhosted.org/packages/f1/60/b22cdbee7e4013dab8b0d7647fc6181120fbbbc8f7025c226d15bd5a47fc/charset_normalizer-3.4.9-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:bd47ba7fc3ca94896759ea0109775132d3e7ab921fbf54038e1bab2e46c313c9", size = 232003, upload-time = "2026-07-07T14:33:40.059Z" }, + { url = "https://files.pythonhosted.org/packages/ea/f8/72eb13dcabe7257035cea8aefd922caad2f110d252bf9f67c4c2ca763aee/charset_normalizer-3.4.9-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:84fd18bcc17526fc2b3c1af7d2b9217d32c9c04448c16ec693b9b4f1985c3d33", size = 223149, upload-time = "2026-07-07T14:33:41.631Z" }, + { url = "https://files.pythonhosted.org/packages/b0/3e/faee8f9de92b14ee1198e9163252bb15efee7301b31256a3b6d9ebfdd0dd/charset_normalizer-3.4.9-cp313-cp313-manylinux_2_31_armv7l.whl", hash = "sha256:5b10cd92fc5c498b35a8635df6d5a100207f88b63a4dc1de7ef9a548e1e2cd63", size = 207901, upload-time = "2026-07-07T14:33:43.209Z" }, + { url = "https://files.pythonhosted.org/packages/3a/25/45f30093ae27dd7b92a793b61882a38685f993700113ca36e0c9c14965e1/charset_normalizer-3.4.9-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a4fbdde9dd4a9ce5fd52c2b3a347bb50cc89483ef783f1cb00d408c13f7a96c0", size = 219176, upload-time = "2026-07-07T14:33:44.725Z" }, + { url = "https://files.pythonhosted.org/packages/48/18/c8f397329c35e32f6a837e488986f4ae03bd2abebc453b48714991630c2f/charset_normalizer-3.4.9-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:416c229f77e5ea25b3dfd4b582f8d73d7e43c22320302b9ab128a2d3a0b38efe", size = 217356, upload-time = "2026-07-07T14:33:46.192Z" }, + { url = "https://files.pythonhosted.org/packages/86/7e/5ce0bba863470fd1902d5e5843968951bddf38abe4742fc97116ef4598b3/charset_normalizer-3.4.9-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:75286256590a6320cf106a0d28970d3560aad9ee09aa7b34fb40524792436d35", size = 209614, upload-time = "2026-07-07T14:33:47.705Z" }, + { url = "https://files.pythonhosted.org/packages/6c/ef/2473d3c4d869155be4af1191111d59c4d5c4e0173026f7e85b176e23bf65/charset_normalizer-3.4.9-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:69b157c5d3292bcd443faca052f3096f637f1e074b98212a933c074ae23dc3b8", size = 224991, upload-time = "2026-07-07T14:33:49.238Z" }, + { url = "https://files.pythonhosted.org/packages/d0/a3/53ddae3db108a088156aa8ddfafd411ebbc1340f48c5573f697b27f69a39/charset_normalizer-3.4.9-cp313-cp313-win32.whl", hash = "sha256:51307f5c71007673a2bf8232ad973483d281e74cb99c8c5a990af1eefa6277d9", size = 150622, upload-time = "2026-07-07T14:33:50.711Z" }, + { url = "https://files.pythonhosted.org/packages/e8/ef/6953a77c7cf2c2ff9998e6f575ab3e380119f100223381565a4f94c1f836/charset_normalizer-3.4.9-cp313-cp313-win_amd64.whl", hash = "sha256:fe2c7201c642b7c308f1675355ad7ff7b66acfe3541625efe5a3ad38f29d6115", size = 161947, upload-time = "2026-07-07T14:33:52.197Z" }, + { url = "https://files.pythonhosted.org/packages/6e/fb/d560d1d1555debbfe7849d9cac6145c1b537709d79576bf22557ed803b82/charset_normalizer-3.4.9-cp313-cp313-win_arm64.whl", hash = "sha256:611057cc5d5c0afc743ba8be6bd828c17e0aaa8643f9d0a9b9bb7dea80eb8012", size = 152594, upload-time = "2026-07-07T14:33:53.486Z" }, + { url = "https://files.pythonhosted.org/packages/7e/8d/496817fa0944239ecae662dd57ea765cfeaec6a735f9f025d4b7b72e7143/charset_normalizer-3.4.9-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:0327fcd59a935777d83410750c50600ee9571af2846f71ce40f25b13da1ef380", size = 317253, upload-time = "2026-07-07T14:33:54.994Z" }, + { url = "https://files.pythonhosted.org/packages/2b/f9/ef4a69ea338ad3c0deceea0f5f7d2380ae8b52132b06d652cb0d2cd86706/charset_normalizer-3.4.9-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8a79d9f4d8001473a30c163556b3c3bfebec837495a412dde78b51672f6134f9", size = 215898, upload-time = "2026-07-07T14:33:56.334Z" }, + { url = "https://files.pythonhosted.org/packages/8c/e7/5ddfd76fc061eb52de219658a4aa431cbacadf0a0219c8854f00da50d289/charset_normalizer-3.4.9-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:33bdcc2a32c0a0e861f60841a512c8acc658c87c2ac59d89e3a46dacf7d866e4", size = 236718, upload-time = "2026-07-07T14:33:57.9Z" }, + { url = "https://files.pythonhosted.org/packages/49/ba/768fa3f36048d81c477a0ce61f813bc1454d80917ccfe550abd9f44f5e24/charset_normalizer-3.4.9-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f840ed6d8ecba8255df8c42b87fadeda98ddfc6eeec05e2dc66e26d46dd6f58a", size = 232519, upload-time = "2026-07-07T14:33:59.811Z" }, + { url = "https://files.pythonhosted.org/packages/f4/c4/b3e049d2aa3766180c78507110543d9d50894cc97f57de543f1be521dcdc/charset_normalizer-3.4.9-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c25fe15c70c59eb7c5ce8c06a1f3fa1da0ecc5ea1e7a5922c40fd2fa9b0d5046", size = 223143, upload-time = "2026-07-07T14:34:01.517Z" }, + { url = "https://files.pythonhosted.org/packages/19/79/55c32d06d76ae4feafe053f061f3e3ab70bcf19f4007797ce8c3efda7830/charset_normalizer-3.4.9-cp314-cp314-manylinux_2_31_armv7l.whl", hash = "sha256:f7fb7d750cfa0a070d2c24e831fd3481019a60dd317ea2b39acbcebc08b6ed81", size = 206742, upload-time = "2026-07-07T14:34:03.04Z" }, + { url = "https://files.pythonhosted.org/packages/10/e0/47c079dd82d217c807479cd59ffd30af56307ea31c108b75758970459ad3/charset_normalizer-3.4.9-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:4d1c96a7a18b9690a4d46df09e3e3382406ae3213727cd1019ebade1c4a81917", size = 219191, upload-time = "2026-07-07T14:34:04.657Z" }, + { url = "https://files.pythonhosted.org/packages/42/ab/b9bc2e77d6b44a7e46ef62ec5cac1c9a6ba7b9135a5d560f002696ec9995/charset_normalizer-3.4.9-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:a4cfde78a9f2880208d16a93b795726a3017d5977e08d1e162a7a31322479c41", size = 218328, upload-time = "2026-07-07T14:34:06.115Z" }, + { url = "https://files.pythonhosted.org/packages/f1/78/c9c71d599f5aa2d42bcdd35cbbd46d7f535351a57e40ff7d8e5a7e219401/charset_normalizer-3.4.9-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:d4d6fcde76f94f5cb9e43e9e9a61f16dacefd228cbbf6f1a09bd9b219a92f1a1", size = 207406, upload-time = "2026-07-07T14:34:07.554Z" }, + { url = "https://files.pythonhosted.org/packages/f6/39/c914445c321a845097ce4f6ac7de9a18228a77b766272125a1ce00d851eb/charset_normalizer-3.4.9-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:898f0e9068ca27d37f8e83a5b962821df851532e6c4a7d615c1c033f9da6eedf", size = 225157, upload-time = "2026-07-07T14:34:09.061Z" }, + { url = "https://files.pythonhosted.org/packages/9b/f2/c0d4b8508565a36bc5c624e88ed297f5b0b1095011034d7f5b83a69908b5/charset_normalizer-3.4.9-cp314-cp314-win32.whl", hash = "sha256:c1c948747b03be832dceed96ca815cef7360de9aa19d37c730f8e3f6101aca48", size = 151095, upload-time = "2026-07-07T14:34:10.901Z" }, + { url = "https://files.pythonhosted.org/packages/49/fd/a1d26144398c67486422a72bf5812cda22cb4ccfcd95a290fb41ceb4b8e2/charset_normalizer-3.4.9-cp314-cp314-win_amd64.whl", hash = "sha256:16b65ea0f2465b6fb52aa22de5eca612aa964ddfec00a912e26f4656cbef890b", size = 162796, upload-time = "2026-07-07T14:34:12.47Z" }, + { url = "https://files.pythonhosted.org/packages/20/95/d75e82f8ce9fd323ebf059c16c9aadefb22a1ecde13b7840b35835e4886c/charset_normalizer-3.4.9-cp314-cp314-win_arm64.whl", hash = "sha256:40a126142a56b2dfc0aacbad1de8310cbf60da7656db0e6b16eebd48e3e93519", size = 153334, upload-time = "2026-07-07T14:34:14.044Z" }, + { url = "https://files.pythonhosted.org/packages/00/5e/17398df3a139985ba9d11ed072531986f408c8fca952835ef1ab1820c02b/charset_normalizer-3.4.9-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:609b3ba8fcc0fb5ab7af00719d0fb6ad0cb518e48e7712d12fd68f1327951198", size = 338848, upload-time = "2026-07-07T14:34:15.688Z" }, + { url = "https://files.pythonhosted.org/packages/cd/91/7253a32e86b7e1d1239b1b36ba6dd0f021a21107ab33054b53119cc083b9/charset_normalizer-3.4.9-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:51447e9aa2684679af07ca5021c3db526e0284347ebf4ffcec1154c3350cfe32", size = 223022, upload-time = "2026-07-07T14:34:17.248Z" }, + { url = "https://files.pythonhosted.org/packages/cb/32/2e64bd2be10e89c61e57ebe6a93fd98ae88eb7ebe414b5121f22c96c69eb/charset_normalizer-3.4.9-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:cc1b0fff8ead343dae06305f954eb8468ba0ec1a97881f42489d198e4ce3c632", size = 241590, upload-time = "2026-07-07T14:34:18.813Z" }, + { url = "https://files.pythonhosted.org/packages/3d/ef/d96ec496cfea0c21db43b0ad03891308b02388d054cc902cf0e5a1ad6a88/charset_normalizer-3.4.9-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:fa36ec09ef71d158186bc79e359ff5fdd6e7996fe8ab638f00d6b93139ba4fcf", size = 239584, upload-time = "2026-07-07T14:34:20.52Z" }, + { url = "https://files.pythonhosted.org/packages/d4/ce/9af95f7876194bd7a14e3dfe4a4de2e0bff02666a3910d72beafd06cc297/charset_normalizer-3.4.9-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:df115d4d83168fdf2cae48ef1ff6d1cb4c466364e30861b37121de0f3bf1b990", size = 230224, upload-time = "2026-07-07T14:34:22.189Z" }, + { url = "https://files.pythonhosted.org/packages/52/94/af74dde74a3996bd959c350709bfe50e297823d70a8c1cbd54b838880863/charset_normalizer-3.4.9-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:f86c6358749bd4fda175388691e3ba8c46e24c5347d0afd20f9b7edfc9faf07d", size = 212667, upload-time = "2026-07-07T14:34:23.857Z" }, + { url = "https://files.pythonhosted.org/packages/ee/f0/f1c4fe746c395922961b5916ed1d7d6e7d4c84851d19ed43cc89980ec953/charset_normalizer-3.4.9-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:32286a2c8d167e897177b673176c1e3e00d4057caf5d2b64eef9a3666b03018e", size = 227179, upload-time = "2026-07-07T14:34:25.586Z" }, + { url = "https://files.pythonhosted.org/packages/e4/56/6c745619ac397e8871e2bcd3cea1eec86b877488f33888b3aef5c3ed506e/charset_normalizer-3.4.9-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:83aed2c10721ddd90f68140685391b50811a880af20654c59af6b6c66c40513c", size = 225372, upload-time = "2026-07-07T14:34:27.212Z" }, + { url = "https://files.pythonhosted.org/packages/78/ad/98aae8630ac71f16711968e38a5acfecce41b778bf2f0312851020f565a8/charset_normalizer-3.4.9-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:cd6c3d4b783c556fa00bf540854e42f135e2f256abd29669fcd0da0f2dec79c2", size = 215222, upload-time = "2026-07-07T14:34:28.774Z" }, + { url = "https://files.pythonhosted.org/packages/f7/40/9593d54209765207a7f11073c06494c1721e4ca4a0a426c597679bf7f91e/charset_normalizer-3.4.9-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ee2f2a527e3c1a6e6411eb4209642e138b544a2d72fe5d0d76daf77b24063534", size = 231958, upload-time = "2026-07-07T14:34:30.345Z" }, + { url = "https://files.pythonhosted.org/packages/b1/27/693ee5e8a18191eb38647360c51cd505013e2bd3b366aa43fd5344c21e3c/charset_normalizer-3.4.9-cp314-cp314t-win32.whl", hash = "sha256:0d861473f743244d349b50f850d10eb87aeb22bbdcc8e64f79273c94af5a8226", size = 155580, upload-time = "2026-07-07T14:34:31.884Z" }, + { url = "https://files.pythonhosted.org/packages/80/3f/bd97d3d9c613013d07cb7733d299385b41df37f0471310f5a73dc359f0b8/charset_normalizer-3.4.9-cp314-cp314t-win_amd64.whl", hash = "sha256:9b8e0f3107e2200b76f6054de99016eac3ee6762713587b36baaa7e4bd2ae177", size = 167620, upload-time = "2026-07-07T14:34:33.438Z" }, + { url = "https://files.pythonhosted.org/packages/3d/c6/eee9dca4439b1061f76373f06ea855678cc4a64c1c3c90b50e479edbb8eb/charset_normalizer-3.4.9-cp314-cp314t-win_arm64.whl", hash = "sha256:19ac87f93086ce37b86e098888555c4b4bc48102279bae3350098c0ed664b501", size = 158037, upload-time = "2026-07-07T14:34:35.018Z" }, + { url = "https://files.pythonhosted.org/packages/98/2b/f97f1c193fb855c345d678f5077d6926034db0722df74c8f057020e05a25/charset_normalizer-3.4.9-py3-none-any.whl", hash = "sha256:68e5f26a1ad57ded6d1cfb85331d1c1a195314756471d97758c48498bb4dcdf5", size = 64538, upload-time = "2026-07-07T14:34:56.993Z" }, ] [[package]] name = "colorama" version = "0.4.6" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697 } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335 }, + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, ] [[package]] @@ -280,108 +336,109 @@ name = "contourpy" version = "1.3.3" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "numpy" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/58/01/1253e6698a07380cd31a736d248a3f2a50a7c88779a1813da27503cadc2a/contourpy-1.3.3.tar.gz", hash = "sha256:083e12155b210502d0bca491432bb04d56dc3432f95a979b429f2848c3dbe880", size = 13466174 } -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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { url = "https://files.pythonhosted.org/packages/75/3e/f2cc6cd56dc8cff46b1a56232eabc6feea52720083ea71ab15523daab796/contourpy-1.3.3-cp311-cp311-win32.whl", hash = "sha256:fd907ae12cd483cd83e414b12941c632a969171bf90fc937d0c9f268a31cafff", size = 183677 }, - { url = "https://files.pythonhosted.org/packages/98/4b/9bd370b004b5c9d8045c6c33cf65bae018b27aca550a3f657cdc99acdbd8/contourpy-1.3.3-cp311-cp311-win_amd64.whl", hash = "sha256:3519428f6be58431c56581f1694ba8e50626f2dd550af225f82fb5f5814d2a42", size = 225234 }, - { url = "https://files.pythonhosted.org/packages/d9/b6/71771e02c2e004450c12b1120a5f488cad2e4d5b590b1af8bad060360fe4/contourpy-1.3.3-cp311-cp311-win_arm64.whl", hash = "sha256:15ff10bfada4bf92ec8b31c62bf7c1834c244019b4a33095a68000d7075df470", size = 193123 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { url = "https://files.pythonhosted.org/packages/cf/8f/5847f44a7fddf859704217a99a23a4f6417b10e5ab1256a179264561540e/contourpy-1.3.3-cp312-cp312-win32.whl", hash = "sha256:023b44101dfe49d7d53932be418477dba359649246075c996866106da069af69", size = 185018 }, - { url = "https://files.pythonhosted.org/packages/19/e8/6026ed58a64563186a9ee3f29f41261fd1828f527dd93d33b60feca63352/contourpy-1.3.3-cp312-cp312-win_amd64.whl", hash = "sha256:8153b8bfc11e1e4d75bcb0bff1db232f9e10b274e0929de9d608027e0d34ff8b", size = 226567 }, - { url = "https://files.pythonhosted.org/packages/d1/e2/f05240d2c39a1ed228d8328a78b6f44cd695f7ef47beb3e684cf93604f86/contourpy-1.3.3-cp312-cp312-win_arm64.whl", hash = "sha256:07ce5ed73ecdc4a03ffe3e1b3e3c1166db35ae7584be76f65dbbe28a7791b0cc", size = 193655 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { url = "https://files.pythonhosted.org/packages/a6/2e/adc197a37443f934594112222ac1aa7dc9a98faf9c3842884df9a9d8751d/contourpy-1.3.3-cp313-cp313-win32.whl", hash = "sha256:b20c7c9a3bf701366556e1b1984ed2d0cedf999903c51311417cf5f591d8c78d", size = 185024 }, - { url = "https://files.pythonhosted.org/packages/18/0b/0098c214843213759692cc638fce7de5c289200a830e5035d1791d7a2338/contourpy-1.3.3-cp313-cp313-win_amd64.whl", hash = "sha256:1cadd8b8969f060ba45ed7c1b714fe69185812ab43bd6b86a9123fe8f99c3263", size = 226578 }, - { url = "https://files.pythonhosted.org/packages/8a/9a/2f6024a0c5995243cd63afdeb3651c984f0d2bc727fd98066d40e141ad73/contourpy-1.3.3-cp313-cp313-win_arm64.whl", hash = "sha256:fd914713266421b7536de2bfa8181aa8c699432b6763a0ea64195ebe28bff6a9", size = 193524 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { url = "https://files.pythonhosted.org/packages/b9/70/f308384a3ae9cd2209e0849f33c913f658d3326900d0ff5d378d6a1422d2/contourpy-1.3.3-cp313-cp313t-win32.whl", hash = "sha256:283edd842a01e3dcd435b1c5116798d661378d83d36d337b8dde1d16a5fc9ba3", size = 196157 }, - { url = "https://files.pythonhosted.org/packages/b2/dd/880f890a6663b84d9e34a6f88cded89d78f0091e0045a284427cb6b18521/contourpy-1.3.3-cp313-cp313t-win_amd64.whl", hash = "sha256:87acf5963fc2b34825e5b6b048f40e3635dd547f590b04d2ab317c2619ef7ae8", size = 240570 }, - { url = "https://files.pythonhosted.org/packages/80/99/2adc7d8ffead633234817ef8e9a87115c8a11927a94478f6bb3d3f4d4f7d/contourpy-1.3.3-cp313-cp313t-win_arm64.whl", hash = "sha256:3c30273eb2a55024ff31ba7d052dde990d7d8e5450f4bbb6e913558b3d6c2301", size = 199713 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { url = "https://files.pythonhosted.org/packages/e2/e2/366af18a6d386f41132a48f033cbd2102e9b0cf6345d35ff0826cd984566/contourpy-1.3.3-cp314-cp314-win32.whl", hash = "sha256:66c8a43a4f7b8df8b71ee1840e4211a3c8d93b214b213f590e18a1beca458f7d", size = 189692 }, - { url = "https://files.pythonhosted.org/packages/7d/c2/57f54b03d0f22d4044b8afb9ca0e184f8b1afd57b4f735c2fa70883dc601/contourpy-1.3.3-cp314-cp314-win_amd64.whl", hash = "sha256:cf9022ef053f2694e31d630feaacb21ea24224be1c3ad0520b13d844274614fd", size = 232424 }, - { url = "https://files.pythonhosted.org/packages/18/79/a9416650df9b525737ab521aa181ccc42d56016d2123ddcb7b58e926a42c/contourpy-1.3.3-cp314-cp314-win_arm64.whl", hash = "sha256:95b181891b4c71de4bb404c6621e7e2390745f887f2a026b2d99e92c17892339", size = 198300 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { url = "https://files.pythonhosted.org/packages/93/8a/68a4ec5c55a2971213d29a9374913f7e9f18581945a7a31d1a39b5d2dfe5/contourpy-1.3.3-cp314-cp314t-win32.whl", hash = "sha256:e74a9a0f5e3fff48fb5a7f2fd2b9b70a3fe014a67522f79b7cca4c0c7e43c9ae", size = 202428 }, - { url = "https://files.pythonhosted.org/packages/fa/96/fd9f641ffedc4fa3ace923af73b9d07e869496c9cc7a459103e6e978992f/contourpy-1.3.3-cp314-cp314t-win_amd64.whl", hash = "sha256:13b68d6a62db8eafaebb8039218921399baf6e47bf85006fd8529f2a08ef33fc", size = 250331 }, - { url = "https://files.pythonhosted.org/packages/ae/8c/469afb6465b853afff216f9528ffda78a915ff880ed58813ba4faf4ba0b6/contourpy-1.3.3-cp314-cp314t-win_arm64.whl", hash = "sha256:b7448cb5a725bb1e35ce88771b86fba35ef418952474492cf7c764059933ff8b", size = 203831 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { url = "https://files.pythonhosted.org/packages/0c/58/bd257695f39d05594ca4ad60df5bcb7e32247f9951fd09a9b8edb82d1daa/contourpy-1.3.3-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:3d1a3799d62d45c18bafd41c5fa05120b96a28079f2393af559b843d1a966a77", size = 225315 }, + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, + { name = "numpy", version = "2.5.1", 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 = "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 } +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 }, + { 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 = "distlib" -version = "0.4.0" +version = "0.4.3" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/96/8e/709914eb2b5749865801041647dc7f4e6d00b549cfe88b65ca192995f07c/distlib-0.4.0.tar.gz", hash = "sha256:feec40075be03a04501a973d81f633735b4b69f98b05450592310c0f401a4e0d", size = 614605 } +sdist = { url = "https://files.pythonhosted.org/packages/c9/02/bd72be9134d25ed783ecbbc38a539ffaefbf90c78418c7fb7229600dbac7/distlib-0.4.3.tar.gz", hash = "sha256:f152097224a0ae24be5a0f6bae1b9359af82133bce63f98a95f86cae1aede9ed", size = 615141, upload-time = "2026-06-12T08:04:52.847Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/33/6b/e0547afaf41bf2c42e52430072fa5658766e3d65bd4b03a563d1b6336f57/distlib-0.4.0-py2.py3-none-any.whl", hash = "sha256:9659f7d87e46584a30b5780e43ac7a2143098441670ff0a49d5f9034c54a6c16", size = 469047 }, + { url = "https://files.pythonhosted.org/packages/02/08/9c41fb51ab5b43eb21674aff13df270e8ba6c4b29c8624e328dc7a9482af/distlib-0.4.3-py2.py3-none-any.whl", hash = "sha256:4b0ce306c966eb73bc3a7b6abad017c556dadd92c44701562cd528ac7fde4d5b", size = 470628, upload-time = "2026-06-12T08:04:50.506Z" }, ] [[package]] name = "filelock" -version = "3.28.0" +version = "3.32.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d6/17/6e8890271880903e3538660a21d63a6c1fea969ac71d0d6b608b78727fa9/filelock-3.28.0.tar.gz", hash = "sha256:4ed1010aae813c4ee8d9c660e4792475ee60c4a0ba76073ceaf862bd317e3ca6", size = 56474 } +sdist = { url = "https://files.pythonhosted.org/packages/f6/57/3ba6e6cb097f85b855b00163d169f35365f44277df044dcf96d55b8f62a3/filelock-3.32.2.tar.gz", hash = "sha256:c33351e1f49cae33414acbc6d56784e6ecee82514ec90795da1161fc4836b5b8", size = 217172, upload-time = "2026-07-29T22:46:04.895Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/3b/21/2f728888c45033d34a417bfcd248ea2564c9e08ab1bfd301377cf05d5586/filelock-3.28.0-py3-none-any.whl", hash = "sha256:de9af6712788e7171df1b28b15eba2446c69721433fa427a9bee07b17820a9db", size = 39189 }, + { url = "https://files.pythonhosted.org/packages/c1/e8/72f8cef9fdfeffe06213fe8508039396ee48daa0e3259457ed766173bfd6/filelock-3.32.2-py3-none-any.whl", hash = "sha256:87dd94cf281e586d135fa51132b8e3d9a598b316e90377a288663c9321036c82", size = 98830, upload-time = "2026-07-29T22:46:03.52Z" }, ] [[package]] @@ -389,74 +446,74 @@ 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 }, + { 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.62.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/9a/08/7012b00a9a5874311b639c3920270c36ee0c445b69d9989a85e5c92ebcb0/fonttools-4.62.1.tar.gz", hash = "sha256:e54c75fd6041f1122476776880f7c3c3295ffa31962dc6ebe2543c00dca58b5d", size = 3580737 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/88/39/23ff32561ec8d45a4d48578b4d241369d9270dc50926c017570e60893701/fonttools-4.62.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:40975849bac44fb0b9253d77420c6d8b523ac4dcdcefeff6e4d706838a5b80f7", size = 2871039 }, - { url = "https://files.pythonhosted.org/packages/24/7f/66d3f8a9338a9b67fe6e1739f47e1cd5cee78bd3bc1206ef9b0b982289a5/fonttools-4.62.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:9dde91633f77fa576879a0c76b1d89de373cae751a98ddf0109d54e173b40f14", size = 2416346 }, - { url = "https://files.pythonhosted.org/packages/aa/53/5276ceba7bff95da7793a07c5284e1da901cf00341ce5e2f3273056c0cca/fonttools-4.62.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6acb4109f8bee00fec985c8c7afb02299e35e9c94b57287f3ea542f28bd0b0a7", size = 5100897 }, - { url = "https://files.pythonhosted.org/packages/cc/a1/40a5c4d8e28b0851d53a8eeeb46fbd73c325a2a9a165f290a5ed90e6c597/fonttools-4.62.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1c5c25671ce8805e0d080e2ffdeca7f1e86778c5cbfbeae86d7f866d8830517b", size = 5071078 }, - { url = "https://files.pythonhosted.org/packages/e3/be/d378fca4c65ea1956fee6d90ace6e861776809cbbc5af22388a090c3c092/fonttools-4.62.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:a5d8825e1140f04e6c99bb7d37a9e31c172f3bc208afbe02175339e699c710e1", size = 5076908 }, - { url = "https://files.pythonhosted.org/packages/f8/d9/ae6a1d0693a4185a84605679c8a1f719a55df87b9c6e8e817bfdd9ef5936/fonttools-4.62.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:268abb1cb221e66c014acc234e872b7870d8b5d4657a83a8f4205094c32d2416", size = 5202275 }, - { url = "https://files.pythonhosted.org/packages/54/6c/af95d9c4efb15cabff22642b608342f2bd67137eea6107202d91b5b03184/fonttools-4.62.1-cp311-cp311-win32.whl", hash = "sha256:942b03094d7edbb99bdf1ae7e9090898cad7bf9030b3d21f33d7072dbcb51a53", size = 2293075 }, - { url = "https://files.pythonhosted.org/packages/d3/97/bf54c5b3f2be34e1f143e6db838dfdc54f2ffa3e68c738934c82f3b2a08d/fonttools-4.62.1-cp311-cp311-win_amd64.whl", hash = "sha256:e8514f4924375f77084e81467e63238b095abda5107620f49421c368a6017ed2", size = 2344593 }, - { url = "https://files.pythonhosted.org/packages/47/d4/dbacced3953544b9a93088cc10ef2b596d348c983d5c67a404fa41ec51ba/fonttools-4.62.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:90365821debbd7db678809c7491ca4acd1e0779b9624cdc6ddaf1f31992bf974", size = 2870219 }, - { url = "https://files.pythonhosted.org/packages/66/9e/a769c8e99b81e5a87ab7e5e7236684de4e96246aae17274e5347d11ebd78/fonttools-4.62.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:12859ff0b47dd20f110804c3e0d0970f7b832f561630cd879969011541a464a9", size = 2414891 }, - { url = "https://files.pythonhosted.org/packages/69/64/f19a9e3911968c37e1e620e14dfc5778299e1474f72f4e57c5ec771d9489/fonttools-4.62.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9c125ffa00c3d9003cdaaf7f2c79e6e535628093e14b5de1dccb08859b680936", size = 5033197 }, - { url = "https://files.pythonhosted.org/packages/9b/8a/99c8b3c3888c5c474c08dbfd7c8899786de9604b727fcefb055b42c84bba/fonttools-4.62.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:149f7d84afca659d1a97e39a4778794a2f83bf344c5ee5134e09995086cc2392", size = 4988768 }, - { url = "https://files.pythonhosted.org/packages/d1/c6/0f904540d3e6ab463c1243a0d803504826a11604c72dd58c2949796a1762/fonttools-4.62.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:0aa72c43a601cfa9273bb1ae0518f1acadc01ee181a6fc60cd758d7fdadffc04", size = 4971512 }, - { url = "https://files.pythonhosted.org/packages/29/0b/5cbef6588dc9bd6b5c9ad6a4d5a8ca384d0cea089da31711bbeb4f9654a6/fonttools-4.62.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:19177c8d96c7c36359266e571c5173bcee9157b59cfc8cb0153c5673dc5a3a7d", size = 5122723 }, - { url = "https://files.pythonhosted.org/packages/4a/47/b3a5342d381595ef439adec67848bed561ab7fdb1019fa522e82101b7d9c/fonttools-4.62.1-cp312-cp312-win32.whl", hash = "sha256:a24decd24d60744ee8b4679d38e88b8303d86772053afc29b19d23bb8207803c", size = 2281278 }, - { url = "https://files.pythonhosted.org/packages/28/b1/0c2ab56a16f409c6c8a68816e6af707827ad5d629634691ff60a52879792/fonttools-4.62.1-cp312-cp312-win_amd64.whl", hash = "sha256:9e7863e10b3de72376280b515d35b14f5eeed639d1aa7824f4cf06779ec65e42", size = 2331414 }, - { url = "https://files.pythonhosted.org/packages/3b/56/6f389de21c49555553d6a5aeed5ac9767631497ac836c4f076273d15bd72/fonttools-4.62.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:c22b1014017111c401469e3acc5433e6acf6ebcc6aa9efb538a533c800971c79", size = 2865155 }, - { url = "https://files.pythonhosted.org/packages/03/c5/0e3966edd5ec668d41dfe418787726752bc07e2f5fd8c8f208615e61fa89/fonttools-4.62.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:68959f5fc58ed4599b44aad161c2837477d7f35f5f79402d97439974faebfebe", size = 2412802 }, - { url = "https://files.pythonhosted.org/packages/52/94/e6ac4b44026de7786fe46e3bfa0c87e51d5d70a841054065d49cd62bb909/fonttools-4.62.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ef46db46c9447103b8f3ff91e8ba009d5fe181b1920a83757a5762551e32bb68", size = 5013926 }, - { url = "https://files.pythonhosted.org/packages/e2/98/8b1e801939839d405f1f122e7d175cebe9aeb4e114f95bfc45e3152af9a7/fonttools-4.62.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:6706d1cb1d5e6251a97ad3c1b9347505c5615c112e66047abbef0f8545fa30d1", size = 4964575 }, - { url = "https://files.pythonhosted.org/packages/46/76/7d051671e938b1881670528fec69cc4044315edd71a229c7fd712eaa5119/fonttools-4.62.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:2e7abd2b1e11736f58c1de27819e1955a53267c21732e78243fa2fa2e5c1e069", size = 4953693 }, - { url = "https://files.pythonhosted.org/packages/1f/ae/b41f8628ec0be3c1b934fc12b84f4576a5c646119db4d3bdd76a217c90b5/fonttools-4.62.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:403d28ce06ebfc547fbcb0cb8b7f7cc2f7a2d3e1a67ba9a34b14632df9e080f9", size = 5094920 }, - { url = "https://files.pythonhosted.org/packages/f2/f6/53a1e9469331a23dcc400970a27a4caa3d9f6edbf5baab0260285238b884/fonttools-4.62.1-cp313-cp313-win32.whl", hash = "sha256:93c316e0f5301b2adbe6a5f658634307c096fd5aae60a5b3412e4f3e1728ab24", size = 2279928 }, - { url = "https://files.pythonhosted.org/packages/38/60/35186529de1db3c01f5ad625bde07c1f576305eab6d86bbda4c58445f721/fonttools-4.62.1-cp313-cp313-win_amd64.whl", hash = "sha256:7aa21ff53e28a9c2157acbc44e5b401149d3c9178107130e82d74ceb500e5056", size = 2330514 }, - { url = "https://files.pythonhosted.org/packages/36/f0/2888cdac391807d68d90dcb16ef858ddc1b5309bfc6966195a459dd326e2/fonttools-4.62.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:fa1d16210b6b10a826d71bed68dd9ec24a9e218d5a5e2797f37c573e7ec215ca", size = 2864442 }, - { url = "https://files.pythonhosted.org/packages/4b/b2/e521803081f8dc35990816b82da6360fa668a21b44da4b53fc9e77efcd62/fonttools-4.62.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:aa69d10ed420d8121118e628ad47d86e4caa79ba37f968597b958f6cceab7eca", size = 2410901 }, - { url = "https://files.pythonhosted.org/packages/00/a4/8c3511ff06e53110039358dbbdc1a65d72157a054638387aa2ada300a8b8/fonttools-4.62.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bd13b7999d59c5eb1c2b442eb2d0c427cb517a0b7a1f5798fc5c9e003f5ff782", size = 4999608 }, - { url = "https://files.pythonhosted.org/packages/28/63/cd0c3b26afe60995a5295f37c246a93d454023726c3261cfbb3559969bb9/fonttools-4.62.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8d337fdd49a79b0d51c4da87bc38169d21c3abbf0c1aa9367eff5c6656fb6dae", size = 4912726 }, - { url = "https://files.pythonhosted.org/packages/70/b9/ac677cb07c24c685cf34f64e140617d58789d67a3dd524164b63648c6114/fonttools-4.62.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:d241cdc4a67b5431c6d7f115fdf63335222414995e3a1df1a41e1182acd4bcc7", size = 4951422 }, - { url = "https://files.pythonhosted.org/packages/e6/10/11c08419a14b85b7ca9a9faca321accccc8842dd9e0b1c8a72908de05945/fonttools-4.62.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:c05557a78f8fa514da0f869556eeda40887a8abc77c76ee3f74cf241778afd5a", size = 5060979 }, - { url = "https://files.pythonhosted.org/packages/4e/3c/12eea4a4cf054e7ab058ed5ceada43b46809fce2bf319017c4d63ae55bb4/fonttools-4.62.1-cp314-cp314-win32.whl", hash = "sha256:49a445d2f544ce4a69338694cad575ba97b9a75fff02720da0882d1a73f12800", size = 2283733 }, - { url = "https://files.pythonhosted.org/packages/6b/67/74b070029043186b5dd13462c958cb7c7f811be0d2e634309d9a1ffb1505/fonttools-4.62.1-cp314-cp314-win_amd64.whl", hash = "sha256:1eecc128c86c552fb963fe846ca4e011b1be053728f798185a1687502f6d398e", size = 2335663 }, - { url = "https://files.pythonhosted.org/packages/42/c5/4d2ed3ca6e33617fc5624467da353337f06e7f637707478903c785bd8e20/fonttools-4.62.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:1596aeaddf7f78e21e68293c011316a25267b3effdaccaf4d59bc9159d681b82", size = 2947288 }, - { url = "https://files.pythonhosted.org/packages/1f/e9/7ab11ddfda48ed0f89b13380e5595ba572619c27077be0b2c447a63ff351/fonttools-4.62.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:8f8fca95d3bb3208f59626a4b0ea6e526ee51f5a8ad5d91821c165903e8d9260", size = 2449023 }, - { url = "https://files.pythonhosted.org/packages/b2/10/a800fa090b5e8819942e54e19b55fc7c21fe14a08757c3aa3ca8db358939/fonttools-4.62.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee91628c08e76f77b533d65feb3fbe6d9dad699f95be51cf0d022db94089cdc4", size = 5137599 }, - { url = "https://files.pythonhosted.org/packages/37/dc/8ccd45033fffd74deb6912fa1ca524643f584b94c87a16036855b498a1ed/fonttools-4.62.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:5f37df1cac61d906e7b836abe356bc2f34c99d4477467755c216b72aa3dc748b", size = 4920933 }, - { url = "https://files.pythonhosted.org/packages/99/eb/e618adefb839598d25ac8136cd577925d6c513dc0d931d93b8af956210f0/fonttools-4.62.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:92bb00a947e666169c99b43753c4305fc95a890a60ef3aeb2a6963e07902cc87", size = 5016232 }, - { url = "https://files.pythonhosted.org/packages/d9/5f/9b5c9bfaa8ec82def8d8168c4f13615990d6ce5996fe52bd49bfb5e05134/fonttools-4.62.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:bdfe592802ef939a0e33106ea4a318eeb17822c7ee168c290273cbd5fabd746c", size = 5042987 }, - { url = "https://files.pythonhosted.org/packages/90/aa/dfbbe24c6a6afc5c203d90cc0343e24bcbb09e76d67c4d6eef8c2558d7ba/fonttools-4.62.1-cp314-cp314t-win32.whl", hash = "sha256:b820fcb92d4655513d8402d5b219f94481c4443d825b4372c75a2072aa4b357a", size = 2348021 }, - { url = "https://files.pythonhosted.org/packages/13/6f/ae9c4e4dd417948407b680855c2c7790efb52add6009aaecff1e3bc50e8e/fonttools-4.62.1-cp314-cp314t-win_amd64.whl", hash = "sha256:59b372b4f0e113d3746b88985f1c796e7bf830dd54b28374cd85c2b8acd7583e", size = 2414147 }, - { url = "https://files.pythonhosted.org/packages/fd/ba/56147c165442cc5ba7e82ecf301c9a68353cede498185869e6e02b4c264f/fonttools-4.62.1-py3-none-any.whl", hash = "sha256:7487782e2113861f4ddcc07c3436450659e3caa5e470b27dc2177cade2d8e7fd", size = 1152647 }, +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.3.0" +version = "2026.7.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e1/cf/b50ddf667c15276a9ab15a70ef5f257564de271957933ffea49d2cdbcdfb/fsspec-2026.3.0.tar.gz", hash = "sha256:1ee6a0e28677557f8c2f994e3eea77db6392b4de9cd1f5d7a9e87a0ae9d01b41", size = 313547 } +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/d5/1f/5f4a3cd9e4440e9d9bc78ad0a91a1c8d46b4d429d5239ebe6793c9fe5c41/fsspec-2026.3.0-py3-none-any.whl", hash = "sha256:d2ceafaad1b3457968ed14efa28798162f1638dbb5d2a6868a2db002a5ee39a4", size = 202595 }, + { 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 = "gast" version = "0.7.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/91/f6/e73969782a2ecec280f8a176f2476149dd9dba69d5f8779ec6108a7721e6/gast-0.7.0.tar.gz", hash = "sha256:0bb14cd1b806722e91ddbab6fb86bba148c22b40e7ff11e248974e04c8adfdae", size = 33630 } +sdist = { url = "https://files.pythonhosted.org/packages/91/f6/e73969782a2ecec280f8a176f2476149dd9dba69d5f8779ec6108a7721e6/gast-0.7.0.tar.gz", hash = "sha256:0bb14cd1b806722e91ddbab6fb86bba148c22b40e7ff11e248974e04c8adfdae", size = 33630, upload-time = "2025-11-29T15:30:05.266Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/1d/33/f1c6a276de27b7d7339a34749cc33fa87f077f921969c47185d34a887ae2/gast-0.7.0-py3-none-any.whl", hash = "sha256:99cbf1365633a74099f69c59bd650476b96baa5ef196fec88032b00b31ba36f7", size = 22966 }, + { url = "https://files.pythonhosted.org/packages/1d/33/f1c6a276de27b7d7339a34749cc33fa87f077f921969c47185d34a887ae2/gast-0.7.0-py3-none-any.whl", hash = "sha256:99cbf1365633a74099f69c59bd650476b96baa5ef196fec88032b00b31ba36f7", size = 22966, upload-time = "2025-11-29T15:30:03.983Z" }, ] [[package]] @@ -466,60 +523,60 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "six" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/35/4a/0bd53b36ff0323d10d5f24ebd67af2de10a1117f5cf4d7add90df92756f1/google-pasta-0.2.0.tar.gz", hash = "sha256:c9f2c8dfc8f96d0d5808299920721be30c9eec37f2389f28904f454565c8a16e", size = 40430 } +sdist = { url = "https://files.pythonhosted.org/packages/35/4a/0bd53b36ff0323d10d5f24ebd67af2de10a1117f5cf4d7add90df92756f1/google-pasta-0.2.0.tar.gz", hash = "sha256:c9f2c8dfc8f96d0d5808299920721be30c9eec37f2389f28904f454565c8a16e", size = 40430, upload-time = "2020-03-13T18:57:50.34Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a3/de/c648ef6835192e6e2cc03f40b19eeda4382c49b5bafb43d88b931c4c74ac/google_pasta-0.2.0-py3-none-any.whl", hash = "sha256:b32482794a366b5366a32c92a9a9201b107821889935a02b3e51f6b432ea84ed", size = 57471 }, + { url = "https://files.pythonhosted.org/packages/a3/de/c648ef6835192e6e2cc03f40b19eeda4382c49b5bafb43d88b931c4c74ac/google_pasta-0.2.0-py3-none-any.whl", hash = "sha256:b32482794a366b5366a32c92a9a9201b107821889935a02b3e51f6b432ea84ed", size = 57471, upload-time = "2020-03-13T18:57:48.872Z" }, ] [[package]] name = "grpcio" -version = "1.80.0" +version = "1.83.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/b7/48/af6173dbca4454f4637a4678b67f52ca7e0c1ed7d5894d89d434fecede05/grpcio-1.80.0.tar.gz", hash = "sha256:29aca15edd0688c22ba01d7cc01cb000d72b2033f4a3c72a81a19b56fd143257", size = 12978905 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/5d/db/1d56e5f5823257b291962d6c0ce106146c6447f405b60b234c4f222a7cde/grpcio-1.80.0-cp311-cp311-linux_armv7l.whl", hash = "sha256:dfab85db094068ff42e2a3563f60ab3dddcc9d6488a35abf0132daec13209c8a", size = 6055009 }, - { url = "https://files.pythonhosted.org/packages/6e/18/c83f3cad64c5ca63bca7e91e5e46b0d026afc5af9d0a9972472ceba294b3/grpcio-1.80.0-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:5c07e82e822e1161354e32da2662f741a4944ea955f9f580ec8fb409dd6f6060", size = 12035295 }, - { url = "https://files.pythonhosted.org/packages/0f/8e/e14966b435be2dda99fbe89db9525ea436edc79780431a1c2875a3582644/grpcio-1.80.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ba0915d51fd4ced2db5ff719f84e270afe0e2d4c45a7bdb1e8d036e4502928c2", size = 6610297 }, - { url = "https://files.pythonhosted.org/packages/cc/26/d5eb38f42ce0e3fdc8174ea4d52036ef8d58cc4426cb800f2610f625dd75/grpcio-1.80.0-cp311-cp311-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:3cb8130ba457d2aa09fa6b7c3ed6b6e4e6a2685fce63cb803d479576c4d80e21", size = 7300208 }, - { url = "https://files.pythonhosted.org/packages/25/51/bd267c989f85a17a5b3eea65a6feb4ff672af41ca614e5a0279cc0ea381c/grpcio-1.80.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:09e5e478b3d14afd23f12e49e8b44c8684ac3c5f08561c43a5b9691c54d136ab", size = 6813442 }, - { url = "https://files.pythonhosted.org/packages/9e/d9/d80eef735b19e9169e30164bbf889b46f9df9127598a83d174eb13a48b26/grpcio-1.80.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:00168469238b022500e486c1c33916acf2f2a9b2c022202cf8a1885d2e3073c1", size = 7414743 }, - { url = "https://files.pythonhosted.org/packages/de/f2/567f5bd5054398ed6b0509b9a30900376dcf2786bd936812098808b49d8d/grpcio-1.80.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:8502122a3cc1714038e39a0b071acb1207ca7844208d5ea0d091317555ee7106", size = 8426046 }, - { url = "https://files.pythonhosted.org/packages/62/29/73ef0141b4732ff5eacd68430ff2512a65c004696997f70476a83e548e7e/grpcio-1.80.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ce1794f4ea6cc3ca29463f42d665c32ba1b964b48958a66497917fe9069f26e6", size = 7851641 }, - { url = "https://files.pythonhosted.org/packages/46/69/abbfa360eb229a8623bab5f5a4f8105e445bd38ce81a89514ba55d281ad0/grpcio-1.80.0-cp311-cp311-win32.whl", hash = "sha256:51b4a7189b0bef2aa30adce3c78f09c83526cf3dddb24c6a96555e3b97340440", size = 4154368 }, - { url = "https://files.pythonhosted.org/packages/6f/d4/ae92206d01183b08613e846076115f5ac5991bae358d2a749fa864da5699/grpcio-1.80.0-cp311-cp311-win_amd64.whl", hash = "sha256:02e64bb0bb2da14d947a49e6f120a75e947250aebe65f9629b62bb1f5c14e6e9", size = 4894235 }, - { url = "https://files.pythonhosted.org/packages/5c/e8/a2b749265eb3415abc94f2e619bbd9e9707bebdda787e61c593004ec927a/grpcio-1.80.0-cp312-cp312-linux_armv7l.whl", hash = "sha256:c624cc9f1008361014378c9d776de7182b11fe8b2e5a81bc69f23a295f2a1ad0", size = 6015616 }, - { url = "https://files.pythonhosted.org/packages/3e/97/b1282161a15d699d1e90c360df18d19165a045ce1c343c7f313f5e8a0b77/grpcio-1.80.0-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:f49eddcac43c3bf350c0385366a58f36bed8cc2c0ec35ef7b74b49e56552c0c2", size = 12014204 }, - { url = "https://files.pythonhosted.org/packages/6e/5e/d319c6e997b50c155ac5a8cb12f5173d5b42677510e886d250d50264949d/grpcio-1.80.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d334591df610ab94714048e0d5b4f3dd5ad1bee74dfec11eee344220077a79de", size = 6563866 }, - { url = "https://files.pythonhosted.org/packages/ae/f6/fdd975a2cb4d78eb67769a7b3b3830970bfa2e919f1decf724ae4445f42c/grpcio-1.80.0-cp312-cp312-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:0cb517eb1d0d0aaf1d87af7cc5b801d686557c1d88b2619f5e31fab3c2315921", size = 7273060 }, - { url = "https://files.pythonhosted.org/packages/db/f0/a3deb5feba60d9538a962913e37bd2e69a195f1c3376a3dd44fe0427e996/grpcio-1.80.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:4e78c4ac0d97dc2e569b2f4bcbbb447491167cb358d1a389fc4af71ab6f70411", size = 6782121 }, - { url = "https://files.pythonhosted.org/packages/ca/84/36c6dcfddc093e108141f757c407902a05085e0c328007cb090d56646cdf/grpcio-1.80.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2ed770b4c06984f3b47eb0517b1c69ad0b84ef3f40128f51448433be904634cd", size = 7383811 }, - { url = "https://files.pythonhosted.org/packages/7c/ef/f3a77e3dc5b471a0ec86c564c98d6adfa3510d38f8ee99010410858d591e/grpcio-1.80.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:256507e2f524092f1473071a05e65a5b10d84b82e3ff24c5b571513cfaa61e2f", size = 8393860 }, - { url = "https://files.pythonhosted.org/packages/9b/8d/9d4d27ed7f33d109c50d6b5ce578a9914aa68edab75d65869a17e630a8d1/grpcio-1.80.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:9a6284a5d907c37db53350645567c522be314bac859a64a7a5ca63b77bb7958f", size = 7830132 }, - { url = "https://files.pythonhosted.org/packages/14/e4/9990b41c6d7a44e1e9dee8ac11d7a9802ba1378b40d77468a7761d1ad288/grpcio-1.80.0-cp312-cp312-win32.whl", hash = "sha256:c71309cfce2f22be26aa4a847357c502db6c621f1a49825ae98aa0907595b193", size = 4140904 }, - { url = "https://files.pythonhosted.org/packages/2f/2c/296f6138caca1f4b92a31ace4ae1b87dab692fc16a7a3417af3bb3c805bf/grpcio-1.80.0-cp312-cp312-win_amd64.whl", hash = "sha256:9fe648599c0e37594c4809d81a9e77bd138cc82eb8baa71b6a86af65426723ff", size = 4880944 }, - { url = "https://files.pythonhosted.org/packages/2f/3a/7c3c25789e3f069e581dc342e03613c5b1cb012c4e8c7d9d5cf960a75856/grpcio-1.80.0-cp313-cp313-linux_armv7l.whl", hash = "sha256:e9e408fc016dffd20661f0126c53d8a31c2821b5c13c5d67a0f5ed5de93319ad", size = 6017243 }, - { url = "https://files.pythonhosted.org/packages/04/19/21a9806eb8240e174fd1ab0cd5b9aa948bb0e05c2f2f55f9d5d7405e6d08/grpcio-1.80.0-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:92d787312e613754d4d8b9ca6d3297e69994a7912a32fa38c4c4e01c272974b0", size = 12010840 }, - { url = "https://files.pythonhosted.org/packages/18/3a/23347d35f76f639e807fb7a36fad3068aed100996849a33809591f26eca6/grpcio-1.80.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:8ac393b58aa16991a2f1144ec578084d544038c12242da3a215966b512904d0f", size = 6567644 }, - { url = "https://files.pythonhosted.org/packages/ff/40/96e07ecb604a6a67ae6ab151e3e35b132875d98bc68ec65f3e5ab3e781d7/grpcio-1.80.0-cp313-cp313-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:68e5851ac4b9afe07e7f84483803ad167852570d65326b34d54ca560bfa53fb6", size = 7277830 }, - { url = "https://files.pythonhosted.org/packages/9b/e2/da1506ecea1f34a5e365964644b35edef53803052b763ca214ba3870c856/grpcio-1.80.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:873ff5d17d68992ef6605330127425d2fc4e77e612fa3c3e0ed4e668685e3140", size = 6783216 }, - { url = "https://files.pythonhosted.org/packages/44/83/3b20ff58d0c3b7f6caaa3af9a4174d4023701df40a3f39f7f1c8e7c48f9d/grpcio-1.80.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:2bea16af2750fd0a899bf1abd9022244418b55d1f37da2202249ba4ba673838d", size = 7385866 }, - { url = "https://files.pythonhosted.org/packages/47/45/55c507599c5520416de5eefecc927d6a0d7af55e91cfffb2e410607e5744/grpcio-1.80.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ba0db34f7e1d803a878284cd70e4c63cb6ae2510ba51937bf8f45ba997cefcf7", size = 8391602 }, - { url = "https://files.pythonhosted.org/packages/10/bb/dd06f4c24c01db9cf11341b547d0a016b2c90ed7dbbb086a5710df7dd1d7/grpcio-1.80.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8eb613f02d34721f1acf3626dfdb3545bd3c8505b0e52bf8b5710a28d02e8aa7", size = 7826752 }, - { url = "https://files.pythonhosted.org/packages/f9/1e/9d67992ba23371fd63d4527096eb8c6b76d74d52b500df992a3343fd7251/grpcio-1.80.0-cp313-cp313-win32.whl", hash = "sha256:93b6f823810720912fd131f561f91f5fed0fda372b6b7028a2681b8194d5d294", size = 4142310 }, - { url = "https://files.pythonhosted.org/packages/cf/e6/283326a27da9e2c3038bc93eeea36fb118ce0b2d03922a9cda6688f53c5b/grpcio-1.80.0-cp313-cp313-win_amd64.whl", hash = "sha256:e172cf795a3ba5246d3529e4d34c53db70e888fa582a8ffebd2e6e48bc0cba50", size = 4882833 }, - { url = "https://files.pythonhosted.org/packages/c5/6d/e65307ce20f5a09244ba9e9d8476e99fb039de7154f37fb85f26978b59c3/grpcio-1.80.0-cp314-cp314-linux_armv7l.whl", hash = "sha256:3d4147a97c8344d065d01bbf8b6acec2cf86fb0400d40696c8bdad34a64ffc0e", size = 6017376 }, - { url = "https://files.pythonhosted.org/packages/69/10/9cef5d9650c72625a699c549940f0abb3c4bfdb5ed45a5ce431f92f31806/grpcio-1.80.0-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:d8e11f167935b3eb089ac9038e1a063e6d7dbe995c0bb4a661e614583352e76f", size = 12018133 }, - { url = "https://files.pythonhosted.org/packages/04/82/983aabaad82ba26113caceeb9091706a0696b25da004fe3defb5b346e15b/grpcio-1.80.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f14b618fc30de822681ee986cfdcc2d9327229dc4c98aed16896761cacd468b9", size = 6574748 }, - { url = "https://files.pythonhosted.org/packages/07/d7/031666ef155aa0bf399ed7e19439656c38bbd143779ae0861b038ce82abd/grpcio-1.80.0-cp314-cp314-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:4ed39fbdcf9b87370f6e8df4e39ca7b38b3e5e9d1b0013c7b6be9639d6578d14", size = 7277711 }, - { url = "https://files.pythonhosted.org/packages/e8/43/f437a78f7f4f1d311804189e8f11fb311a01049b2e08557c1068d470cb2e/grpcio-1.80.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:2dcc70e9f0ba987526e8e8603a610fb4f460e42899e74e7a518bf3c68fe1bf05", size = 6785372 }, - { url = "https://files.pythonhosted.org/packages/93/3d/f6558e9c6296cb4227faa5c43c54a34c68d32654b829f53288313d16a86e/grpcio-1.80.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:448c884b668b868562b1bda833c5fce6272d26e1926ec46747cda05741d302c1", size = 7395268 }, - { url = "https://files.pythonhosted.org/packages/06/21/0fdd77e84720b08843c371a2efa6f2e19dbebf56adc72df73d891f5506f0/grpcio-1.80.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:a1dc80fe55685b4a543555e6eef975303b36c8db1023b1599b094b92aa77965f", size = 8392000 }, - { url = "https://files.pythonhosted.org/packages/f5/68/67f4947ed55d2e69f2cc199ab9fd85e0a0034d813bbeef84df6d2ba4d4b7/grpcio-1.80.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:31b9ac4ad1aa28ffee5503821fafd09e4da0a261ce1c1281c6c8da0423c83b6e", size = 7828477 }, - { url = "https://files.pythonhosted.org/packages/44/b6/8d4096691b2e385e8271911a0de4f35f0a6c7d05aff7098e296c3de86939/grpcio-1.80.0-cp314-cp314-win32.whl", hash = "sha256:367ce30ba67d05e0592470428f0ec1c31714cab9ef19b8f2e37be1f4c7d32fae", size = 4218563 }, - { url = "https://files.pythonhosted.org/packages/e5/8c/bbe6baf2557262834f2070cf668515fa308b2d38a4bbf771f8f7872a7036/grpcio-1.80.0-cp314-cp314-win_amd64.whl", hash = "sha256:3b01e1f5464c583d2f567b2e46ff0d516ef979978f72091fd81f5ab7fa6e2e7f", size = 5019457 }, +sdist = { url = "https://files.pythonhosted.org/packages/0c/98/304898ac4e04e2d5e4e4c2eadc178b1f2a16d5f4bc2f91306c87d64680b9/grpcio-1.83.0.tar.gz", hash = "sha256:7674587248fbbb2ac6e4eecf83a8a0f3d91a928f941de571acfd3a2f007fbc24", size = 13428824, upload-time = "2026-07-23T15:20:37.759Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d3/f6/3b781cd07a715ea5f5125ae264226e7fc4d87603d6d3955022cabfdc5da2/grpcio-1.83.0-cp311-cp311-linux_armv7l.whl", hash = "sha256:8ff0b8767ddd62704e0d9571c1890af08d84a3a689ebba1807e62519d0b3277f", size = 6338720, upload-time = "2026-07-23T15:19:13.177Z" }, + { url = "https://files.pythonhosted.org/packages/21/cc/d14833d15d5984e366f1b027fa78bd038c9b028c66880bffb0f5a4d25ee2/grpcio-1.83.0-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:4772402f43517b4824980be4b3b2274a81eec0004a70009473c31b340d43e223", size = 12178773, upload-time = "2026-07-23T15:19:15.401Z" }, + { url = "https://files.pythonhosted.org/packages/6b/98/8acbb416544e7871132d8e42a07ed70c802d70e6a16c6009e505a34d32a4/grpcio-1.83.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f4cee5fc86e84a0cf7ad1574b454c3320e087c07f55b7df5dc0ac6a873fb90c0", size = 6921203, upload-time = "2026-07-23T15:19:17.824Z" }, + { url = "https://files.pythonhosted.org/packages/45/9c/0fdbfaf4fc54e5c88f6bce4008a065092fe7fbc4460eb5617ae8b20fd505/grpcio-1.83.0-cp311-cp311-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:f5e822a7e7d03282f6ad225e710493c48b9057a353358344a5f7c42b2b37618d", size = 7648508, upload-time = "2026-07-23T15:19:19.685Z" }, + { url = "https://files.pythonhosted.org/packages/f3/ea/107b9dbb2ed3ad14dd774fd3dde7d29ff9938a6c198654becb2c3a0e9a6a/grpcio-1.83.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f5f410d7c2903eabb34789dfd6342eef04af1ad459943936b7e09a9f5bd417b9", size = 7079466, upload-time = "2026-07-23T15:19:21.478Z" }, + { url = "https://files.pythonhosted.org/packages/3b/06/9fa9941089e6fae83b060b6ce61c1e81053e52decae43197245f45e07d36/grpcio-1.83.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:ee94a4016fdf8699fb1fd8a38652475ff677f1c72074cee44deeeb9a7e95e745", size = 7605583, upload-time = "2026-07-23T15:19:23.74Z" }, + { url = "https://files.pythonhosted.org/packages/a8/2f/f10fb56062dc2771c630827a82d9ad0ecd05cad572ea3b08d49f6631680a/grpcio-1.83.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:c6444666317338e903093c7c756e6cc88eee59f798cb8dd41e87725bf54e1617", size = 8637810, upload-time = "2026-07-23T15:19:25.536Z" }, + { url = "https://files.pythonhosted.org/packages/99/55/f84927258f6a1b6ea6dea661fdc6de859b35e560c96f3012d15ccd39f85e/grpcio-1.83.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:aa074041231f03959cb097dd5517b0677b8ea49215bae01d5710a7b69dd59969", size = 8008021, upload-time = "2026-07-23T15:19:27.863Z" }, + { url = "https://files.pythonhosted.org/packages/c9/6b/cdf72161397ccd29d4ca2192f641524536c9cf54ad948c9dd0e0e01138fa/grpcio-1.83.0-cp311-cp311-win32.whl", hash = "sha256:cb056f6e171c42639a50460b2929c82241fda51f71cf3dcdd68090fe45095a45", size = 4404376, upload-time = "2026-07-23T15:19:30.137Z" }, + { url = "https://files.pythonhosted.org/packages/df/ed/e0ffeb4c848699c194dc9fb6a29ab29bcb2b6aac8c416bf18c51bfe8242c/grpcio-1.83.0-cp311-cp311-win_amd64.whl", hash = "sha256:7416952ca770477990257206276999056f8316d79196f2f25942393e58a20b49", size = 5164469, upload-time = "2026-07-23T15:19:31.941Z" }, + { url = "https://files.pythonhosted.org/packages/15/2b/51e32514a4e9b715375c99721aadff0f24164cc2049b8269eda4de82a814/grpcio-1.83.0-cp312-cp312-linux_armv7l.whl", hash = "sha256:28f6c35ac8fcf10e4594f138e468f194360089dde40d126a7033e863fc479930", size = 6303167, upload-time = "2026-07-23T15:19:33.78Z" }, + { url = "https://files.pythonhosted.org/packages/39/33/b5b50fc2c6fbe350e04814047bb2d409feec7b36ef8b170254c050e06bc0/grpcio-1.83.0-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:33898e6a28e4ae598f1577cb1c4fec2a15c033d0ec52b9b45a09610dd045b9da", size = 12160538, upload-time = "2026-07-23T15:19:35.958Z" }, + { url = "https://files.pythonhosted.org/packages/7b/5f/734e72e7b9f79bcf0b2c270b8d3bca0e4ebb97a27a50d06240b145f6d41e/grpcio-1.83.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:6fb8a1dd0c6f0f931e69e9d0dc6d1c406ed2a44fa963414eafba07b7fb685d16", size = 6869310, upload-time = "2026-07-23T15:19:38.607Z" }, + { url = "https://files.pythonhosted.org/packages/a4/17/a1735f215b2a5cd43c38b79eac072ad197e61be9829905b6b29550abd0db/grpcio-1.83.0-cp312-cp312-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:2b5e75c34842cd9c1b95285ca395c6a569664b81e3ffa6b714125922942abaaf", size = 7613472, upload-time = "2026-07-23T15:19:40.645Z" }, + { url = "https://files.pythonhosted.org/packages/b2/78/c9e81f806ac704b6b145cb01628db398985b1f8dfdc10e23b55fb0902b3d/grpcio-1.83.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:aeb339838db07600481ef869507279b75326c75eac6d10f7afa62a0da1d2bcdd", size = 7040616, upload-time = "2026-07-23T15:19:42.349Z" }, + { url = "https://files.pythonhosted.org/packages/9a/ba/94cd5af859876049d340480acbb61a959096c84b567f215534faa78d0424/grpcio-1.83.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:f47d62808b4c0a97b78bff88a6d4ca283a2a492b9a04a87d814af95ca3b9c19c", size = 7570491, upload-time = "2026-07-23T15:19:44.357Z" }, + { url = "https://files.pythonhosted.org/packages/3e/15/108d30d5a5c964312ae8b9cb0e8cc5b3c1cc68d8f757cca52b3565534d26/grpcio-1.83.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:62003babc444a606dcd1f009cd16391ce23669ae4ad6ec267a873da7937a69f5", size = 8605036, upload-time = "2026-07-23T15:19:46.454Z" }, + { url = "https://files.pythonhosted.org/packages/ea/23/3828ae13c3db8233d123ad612747665817b952d8a954f32390230b582336/grpcio-1.83.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1aa567f8c3f19850ffd5d2858c9a8ea7c80f0db6c01186b71eb31e923ec984f5", size = 7981587, upload-time = "2026-07-23T15:19:48.913Z" }, + { url = "https://files.pythonhosted.org/packages/17/5b/77af31228f55f55a2a5112bb0077ad0a1c4d23dbb0c2853a62475bbdcc14/grpcio-1.83.0-cp312-cp312-win32.whl", hash = "sha256:cb2906c61db4f9c64cc360054b5df70eeb81846228e9e56a4944bd415a63dadc", size = 4394004, upload-time = "2026-07-23T15:19:50.618Z" }, + { url = "https://files.pythonhosted.org/packages/c0/da/f706e39550e7a3732ce2b9c5926107a93d74a802775b19b642a6df27dc96/grpcio-1.83.0-cp312-cp312-win_amd64.whl", hash = "sha256:1c699bbb20f143c8f2bff219de578aa2dc1f919399d67dc702b038b986ee62df", size = 5158525, upload-time = "2026-07-23T15:19:52.246Z" }, + { url = "https://files.pythonhosted.org/packages/56/eb/135daaa713f32d33b8f99b4153b3f8dc3b2a124996ac15581bf9ebdad3c3/grpcio-1.83.0-cp313-cp313-linux_armv7l.whl", hash = "sha256:6662f3b1e07cc7493d437351860dc867bddc6a93c83ecf33bbfdaf0c217ab2d0", size = 6304480, upload-time = "2026-07-23T15:19:53.962Z" }, + { url = "https://files.pythonhosted.org/packages/8f/a1/121806ce69f23138dabe06aa595b0e5f1ae051a37e4c1954eed7d692c800/grpcio-1.83.0-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:74fe6f9e8a35c7dbf32255ee154d15e3e5338a81ed39173d079d594d2e544cd1", size = 12154419, upload-time = "2026-07-23T15:19:56.3Z" }, + { url = "https://files.pythonhosted.org/packages/b0/e8/d0389e09cd6b4c4d3089b92967ae4e3ffd64795bd349bf2f85cd6656d3da/grpcio-1.83.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:10b3fa0475eb572c9a81a6fe37fa16a9c500c0c91cfc148cac15692b7e3c2867", size = 6873200, upload-time = "2026-07-23T15:19:58.701Z" }, + { url = "https://files.pythonhosted.org/packages/f8/51/f464c1d211fa50d5adbabe1b2e519948d99c13757052bfc9ea7afa28e284/grpcio-1.83.0-cp313-cp313-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:5f20a988480b0f28207f057f7f7ae1313393c3cef0adcfeae8248f9947eaf881", size = 7618811, upload-time = "2026-07-23T15:20:00.733Z" }, + { url = "https://files.pythonhosted.org/packages/e8/c0/539fe0832f2dd6500a28f5263071623fb34e8d4867aec632ccf81bd21156/grpcio-1.83.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7bd82671b39065ba18cd536e9cd45b27ff649053f81ddd2c6a966d595067080f", size = 7042310, upload-time = "2026-07-23T15:20:02.675Z" }, + { url = "https://files.pythonhosted.org/packages/8c/ca/ccf617d37ffa72567fa8e005ec7090c99da922799be2fb9847c8b21ca18c/grpcio-1.83.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:bc60215b5cb9fc8ca72942c498b551ac2305bd08f6ef8d4e3f0d21b64fbecd61", size = 7575412, upload-time = "2026-07-23T15:20:04.712Z" }, + { url = "https://files.pythonhosted.org/packages/eb/b9/fd8d5245f823a8e0fd35d90e20ea3aa4acd47f8d5318fa8df307df52dec6/grpcio-1.83.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:f1c3e5689d4b90987b1d72022bcfe866a9a3dc66197484cf856d96b6150e7f45", size = 8604248, upload-time = "2026-07-23T15:20:06.77Z" }, + { url = "https://files.pythonhosted.org/packages/14/1e/f37632fc11db72dfa4bba86c3a43e54358e53030df111ecae5e91a733ad6/grpcio-1.83.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a21cb4eeeba124443f399be2e8b624943cde864dcbe588cb42e5c483a52a906c", size = 7977458, upload-time = "2026-07-23T15:20:09.109Z" }, + { url = "https://files.pythonhosted.org/packages/93/b6/d70b69ae5c0cfc341b9ba474980e4ed99cbf05c0e4a14e9eee8cb73db0a5/grpcio-1.83.0-cp313-cp313-win32.whl", hash = "sha256:8fe04f1050a59f875601eb55d42b4f66946fe89817f967e34db1462ccd07dadf", size = 4393993, upload-time = "2026-07-23T15:20:11.017Z" }, + { url = "https://files.pythonhosted.org/packages/0f/13/45d4cccb555cf4c476226979bf3d2fd0b0254216f7564c3a053e35117efc/grpcio-1.83.0-cp313-cp313-win_amd64.whl", hash = "sha256:6e01ecd9d8ef280abe1365138a4dc318f9a5287f4cb1b41d07816f796653f735", size = 5159650, upload-time = "2026-07-23T15:20:12.979Z" }, + { url = "https://files.pythonhosted.org/packages/9c/60/f2cca8147ea213d3e43ae9158d03ad04e020fdf32ff027253e1fe93f921d/grpcio-1.83.0-cp314-cp314-linux_armv7l.whl", hash = "sha256:3f351629f6ae16ecc0ec3553e586a6763ffd9f6114044286d0cbec3e09241bfa", size = 6305607, upload-time = "2026-07-23T15:20:15.353Z" }, + { url = "https://files.pythonhosted.org/packages/d0/ab/d3874931d123a95e83a3ebf8aa04537988fb62425cedb8bf3cefc5ad41b2/grpcio-1.83.0-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:d05ff664100d429335b93c91b8b34ddf9e94a112205e7fa06dede309e44a4e4c", size = 12166617, upload-time = "2026-07-23T15:20:17.435Z" }, + { url = "https://files.pythonhosted.org/packages/92/ff/6f18f9426b69306f4e00a9add3b0ee2748da8aad53836ef80cab0d62d04f/grpcio-1.83.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7936f2a56cf04f6514705c0fedf400971de01b6aa1719327e4718f410a765e2b", size = 6880213, upload-time = "2026-07-23T15:20:19.98Z" }, + { url = "https://files.pythonhosted.org/packages/70/21/706d1147c6b93b98f179240c13991fbcc56880eba0c868abb1ad40d8a0a6/grpcio-1.83.0-cp314-cp314-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:b0a0be840e51b6b7ee9df9269770faf77bdf4b771053c257c21d12bad607714c", size = 7618335, upload-time = "2026-07-23T15:20:22.161Z" }, + { url = "https://files.pythonhosted.org/packages/74/04/1a8443c889115ec9e213a213e86bc93a71ee9088027e5befa09aaa0edd9d/grpcio-1.83.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:009667eaf3dcd5224c713589cdc98e7ca4ed0ff0b61132c6b276e930eb83a2df", size = 7043416, upload-time = "2026-07-23T15:20:24.209Z" }, + { url = "https://files.pythonhosted.org/packages/86/c6/94e0fee5b12bc1da1370185b680988db6f739d19b42d9959db01a7ea50bf/grpcio-1.83.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:bb669918fd88936b15599caff4160a77ab74bdeb25f2231f6e45b61282d6107b", size = 7583253, upload-time = "2026-07-23T15:20:26.313Z" }, + { url = "https://files.pythonhosted.org/packages/a0/97/de1ccb671fb85575bc5192faedf9ecdbdf5b390d2e6584dcf552bcbd370e/grpcio-1.83.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:c19b454d3d3f28db81f2c7c4dbaee96e7f6fd149721733ffe79d6bc530f17404", size = 8605102, upload-time = "2026-07-23T15:20:28.437Z" }, + { url = "https://files.pythonhosted.org/packages/17/0f/0e0ec749a7034ffcbaa050e39779872950ead90c22e7e0116be3f28b2b46/grpcio-1.83.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:61007cd08640abc5c54547ee32505474c482cd733a53cb87551ea81faa6350af", size = 7979826, upload-time = "2026-07-23T15:20:31.182Z" }, + { url = "https://files.pythonhosted.org/packages/83/fa/c3fda157287f64bc65acee6c5aa90c41acf9e0d3a8e69a265eecff6d00a1/grpcio-1.83.0-cp314-cp314-win32.whl", hash = "sha256:32e11c37f5285b0c6fa3042c05fe06903696689749833fc64e67dec71b9bbe33", size = 4471765, upload-time = "2026-07-23T15:20:33.195Z" }, + { url = "https://files.pythonhosted.org/packages/a1/00/b1b26431c9d54eee11724fd6e5585473a2ed47fbc1fb95e5204906a642ce/grpcio-1.83.0-cp314-cp314-win_amd64.whl", hash = "sha256:2bb48cb5e6dd005ca12b89ce4b6ac0b48ff3112c747542ee7986ef611a8ca6d9", size = 5298932, upload-time = "2026-07-23T15:20:35.48Z" }, ] [[package]] @@ -527,81 +584,128 @@ name = "h5py" version = "3.14.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "numpy" }, + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, + { name = "numpy", version = "2.5.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/5d/57/dfb3c5c3f1bf5f5ef2e59a22dec4ff1f3d7408b55bfcefcfb0ea69ef21c6/h5py-3.14.0.tar.gz", hash = "sha256:2372116b2e0d5d3e5e705b7f663f7c8d96fa79a4052d250484ef91d24d6a08f4", size = 424323 } +sdist = { url = "https://files.pythonhosted.org/packages/5d/57/dfb3c5c3f1bf5f5ef2e59a22dec4ff1f3d7408b55bfcefcfb0ea69ef21c6/h5py-3.14.0.tar.gz", hash = "sha256:2372116b2e0d5d3e5e705b7f663f7c8d96fa79a4052d250484ef91d24d6a08f4", size = 424323, upload-time = "2025-06-06T14:06:15.01Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/61/1b/ad24a8ce846cf0519695c10491e99969d9d203b9632c4fcd5004b1641c2e/h5py-3.14.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f30dbc58f2a0efeec6c8836c97f6c94afd769023f44e2bb0ed7b17a16ec46088", size = 3352382 }, - { url = "https://files.pythonhosted.org/packages/36/5b/a066e459ca48b47cc73a5c668e9924d9619da9e3c500d9fb9c29c03858ec/h5py-3.14.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:543877d7f3d8f8a9828ed5df6a0b78ca3d8846244b9702e99ed0d53610b583a8", size = 2852492 }, - { url = "https://files.pythonhosted.org/packages/08/0c/5e6aaf221557314bc15ba0e0da92e40b24af97ab162076c8ae009320a42b/h5py-3.14.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8c497600c0496548810047257e36360ff551df8b59156d3a4181072eed47d8ad", size = 4298002 }, - { url = "https://files.pythonhosted.org/packages/21/d4/d461649cafd5137088fb7f8e78fdc6621bb0c4ff2c090a389f68e8edc136/h5py-3.14.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:723a40ee6505bd354bfd26385f2dae7bbfa87655f4e61bab175a49d72ebfc06b", size = 4516618 }, - { url = "https://files.pythonhosted.org/packages/db/0c/6c3f879a0f8e891625817637fad902da6e764e36919ed091dc77529004ac/h5py-3.14.0-cp311-cp311-win_amd64.whl", hash = "sha256:d2744b520440a996f2dae97f901caa8a953afc055db4673a993f2d87d7f38713", size = 2874888 }, - { url = "https://files.pythonhosted.org/packages/3e/77/8f651053c1843391e38a189ccf50df7e261ef8cd8bfd8baba0cbe694f7c3/h5py-3.14.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:e0045115d83272090b0717c555a31398c2c089b87d212ceba800d3dc5d952e23", size = 3312740 }, - { url = "https://files.pythonhosted.org/packages/ff/10/20436a6cf419b31124e59fefc78d74cb061ccb22213226a583928a65d715/h5py-3.14.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6da62509b7e1d71a7d110478aa25d245dd32c8d9a1daee9d2a42dba8717b047a", size = 2829207 }, - { url = "https://files.pythonhosted.org/packages/3f/19/c8bfe8543bfdd7ccfafd46d8cfd96fce53d6c33e9c7921f375530ee1d39a/h5py-3.14.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:554ef0ced3571366d4d383427c00c966c360e178b5fb5ee5bb31a435c424db0c", size = 4708455 }, - { url = "https://files.pythonhosted.org/packages/86/f9/f00de11c82c88bfc1ef22633557bfba9e271e0cb3189ad704183fc4a2644/h5py-3.14.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0cbd41f4e3761f150aa5b662df991868ca533872c95467216f2bec5fcad84882", size = 4929422 }, - { url = "https://files.pythonhosted.org/packages/7a/6d/6426d5d456f593c94b96fa942a9b3988ce4d65ebaf57d7273e452a7222e8/h5py-3.14.0-cp312-cp312-win_amd64.whl", hash = "sha256:bf4897d67e613ecf5bdfbdab39a1158a64df105827da70ea1d90243d796d367f", size = 2862845 }, - { url = "https://files.pythonhosted.org/packages/6c/c2/7efe82d09ca10afd77cd7c286e42342d520c049a8c43650194928bcc635c/h5py-3.14.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:aa4b7bbce683379b7bf80aaba68e17e23396100336a8d500206520052be2f812", size = 3289245 }, - { url = "https://files.pythonhosted.org/packages/4f/31/f570fab1239b0d9441024b92b6ad03bb414ffa69101a985e4c83d37608bd/h5py-3.14.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:ef9603a501a04fcd0ba28dd8f0995303d26a77a980a1f9474b3417543d4c6174", size = 2807335 }, - { url = "https://files.pythonhosted.org/packages/0d/ce/3a21d87896bc7e3e9255e0ad5583ae31ae9e6b4b00e0bcb2a67e2b6acdbc/h5py-3.14.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e8cbaf6910fa3983c46172666b0b8da7b7bd90d764399ca983236f2400436eeb", size = 4700675 }, - { url = "https://files.pythonhosted.org/packages/e7/ec/86f59025306dcc6deee5fda54d980d077075b8d9889aac80f158bd585f1b/h5py-3.14.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d90e6445ab7c146d7f7981b11895d70bc1dd91278a4f9f9028bc0c95e4a53f13", size = 4921632 }, - { url = "https://files.pythonhosted.org/packages/3f/6d/0084ed0b78d4fd3e7530c32491f2884140d9b06365dac8a08de726421d4a/h5py-3.14.0-cp313-cp313-win_amd64.whl", hash = "sha256:ae18e3de237a7a830adb76aaa68ad438d85fe6e19e0d99944a3ce46b772c69b3", size = 2852929 }, + { url = "https://files.pythonhosted.org/packages/61/1b/ad24a8ce846cf0519695c10491e99969d9d203b9632c4fcd5004b1641c2e/h5py-3.14.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f30dbc58f2a0efeec6c8836c97f6c94afd769023f44e2bb0ed7b17a16ec46088", size = 3352382, upload-time = "2025-06-06T14:04:37.95Z" }, + { url = "https://files.pythonhosted.org/packages/36/5b/a066e459ca48b47cc73a5c668e9924d9619da9e3c500d9fb9c29c03858ec/h5py-3.14.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:543877d7f3d8f8a9828ed5df6a0b78ca3d8846244b9702e99ed0d53610b583a8", size = 2852492, upload-time = "2025-06-06T14:04:42.092Z" }, + { url = "https://files.pythonhosted.org/packages/08/0c/5e6aaf221557314bc15ba0e0da92e40b24af97ab162076c8ae009320a42b/h5py-3.14.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8c497600c0496548810047257e36360ff551df8b59156d3a4181072eed47d8ad", size = 4298002, upload-time = "2025-06-06T14:04:47.106Z" }, + { url = "https://files.pythonhosted.org/packages/21/d4/d461649cafd5137088fb7f8e78fdc6621bb0c4ff2c090a389f68e8edc136/h5py-3.14.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:723a40ee6505bd354bfd26385f2dae7bbfa87655f4e61bab175a49d72ebfc06b", size = 4516618, upload-time = "2025-06-06T14:04:52.467Z" }, + { url = "https://files.pythonhosted.org/packages/db/0c/6c3f879a0f8e891625817637fad902da6e764e36919ed091dc77529004ac/h5py-3.14.0-cp311-cp311-win_amd64.whl", hash = "sha256:d2744b520440a996f2dae97f901caa8a953afc055db4673a993f2d87d7f38713", size = 2874888, upload-time = "2025-06-06T14:04:56.95Z" }, + { url = "https://files.pythonhosted.org/packages/3e/77/8f651053c1843391e38a189ccf50df7e261ef8cd8bfd8baba0cbe694f7c3/h5py-3.14.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:e0045115d83272090b0717c555a31398c2c089b87d212ceba800d3dc5d952e23", size = 3312740, upload-time = "2025-06-06T14:05:01.193Z" }, + { url = "https://files.pythonhosted.org/packages/ff/10/20436a6cf419b31124e59fefc78d74cb061ccb22213226a583928a65d715/h5py-3.14.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6da62509b7e1d71a7d110478aa25d245dd32c8d9a1daee9d2a42dba8717b047a", size = 2829207, upload-time = "2025-06-06T14:05:05.061Z" }, + { url = "https://files.pythonhosted.org/packages/3f/19/c8bfe8543bfdd7ccfafd46d8cfd96fce53d6c33e9c7921f375530ee1d39a/h5py-3.14.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:554ef0ced3571366d4d383427c00c966c360e178b5fb5ee5bb31a435c424db0c", size = 4708455, upload-time = "2025-06-06T14:05:11.528Z" }, + { url = "https://files.pythonhosted.org/packages/86/f9/f00de11c82c88bfc1ef22633557bfba9e271e0cb3189ad704183fc4a2644/h5py-3.14.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0cbd41f4e3761f150aa5b662df991868ca533872c95467216f2bec5fcad84882", size = 4929422, upload-time = "2025-06-06T14:05:18.399Z" }, + { url = "https://files.pythonhosted.org/packages/7a/6d/6426d5d456f593c94b96fa942a9b3988ce4d65ebaf57d7273e452a7222e8/h5py-3.14.0-cp312-cp312-win_amd64.whl", hash = "sha256:bf4897d67e613ecf5bdfbdab39a1158a64df105827da70ea1d90243d796d367f", size = 2862845, upload-time = "2025-06-06T14:05:23.699Z" }, + { url = "https://files.pythonhosted.org/packages/6c/c2/7efe82d09ca10afd77cd7c286e42342d520c049a8c43650194928bcc635c/h5py-3.14.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:aa4b7bbce683379b7bf80aaba68e17e23396100336a8d500206520052be2f812", size = 3289245, upload-time = "2025-06-06T14:05:28.24Z" }, + { url = "https://files.pythonhosted.org/packages/4f/31/f570fab1239b0d9441024b92b6ad03bb414ffa69101a985e4c83d37608bd/h5py-3.14.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:ef9603a501a04fcd0ba28dd8f0995303d26a77a980a1f9474b3417543d4c6174", size = 2807335, upload-time = "2025-06-06T14:05:31.997Z" }, + { url = "https://files.pythonhosted.org/packages/0d/ce/3a21d87896bc7e3e9255e0ad5583ae31ae9e6b4b00e0bcb2a67e2b6acdbc/h5py-3.14.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e8cbaf6910fa3983c46172666b0b8da7b7bd90d764399ca983236f2400436eeb", size = 4700675, upload-time = "2025-06-06T14:05:37.38Z" }, + { url = "https://files.pythonhosted.org/packages/e7/ec/86f59025306dcc6deee5fda54d980d077075b8d9889aac80f158bd585f1b/h5py-3.14.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d90e6445ab7c146d7f7981b11895d70bc1dd91278a4f9f9028bc0c95e4a53f13", size = 4921632, upload-time = "2025-06-06T14:05:43.464Z" }, + { url = "https://files.pythonhosted.org/packages/3f/6d/0084ed0b78d4fd3e7530c32491f2884140d9b06365dac8a08de726421d4a/h5py-3.14.0-cp313-cp313-win_amd64.whl", hash = "sha256:ae18e3de237a7a830adb76aaa68ad438d85fe6e19e0d99944a3ce46b772c69b3", size = 2852929, upload-time = "2025-06-06T14:05:47.659Z" }, ] [[package]] name = "identify" -version = "2.6.18" +version = "2.6.19" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/46/c4/7fb4db12296cdb11893d61c92048fe617ee853f8523b9b296ac03b43757e/identify-2.6.18.tar.gz", hash = "sha256:873ac56a5e3fd63e7438a7ecbc4d91aca692eb3fefa4534db2b7913f3fc352fd", size = 99580 } +sdist = { url = "https://files.pythonhosted.org/packages/52/63/51723b5f116cc04b061cb6f5a561790abf249d25931d515cd375e063e0f4/identify-2.6.19.tar.gz", hash = "sha256:6be5020c38fcb07da56c53733538a3081ea5aa70d36a156f83044bfbf9173842", size = 99567, upload-time = "2026-04-17T18:39:50.265Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/46/33/92ef41c6fad0233e41d3d84ba8e8ad18d1780f1e5d99b3c683e6d7f98b63/identify-2.6.18-py2.py3-none-any.whl", hash = "sha256:8db9d3c8ea9079db92cafb0ebf97abdc09d52e97f4dcf773a2e694048b7cd737", size = 99394 }, + { url = "https://files.pythonhosted.org/packages/94/84/d9273cd09688070a6523c4aee4663a8538721b2b755c4962aafae0011e72/identify-2.6.19-py2.py3-none-any.whl", hash = "sha256:20e6a87f786f768c092a721ad107fc9df0eb89347be9396cadf3f4abbd1fb78a", size = 99397, upload-time = "2026-04-17T18:39:49.221Z" }, ] [[package]] name = "idna" -version = "3.11" +version = "3.18" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/6f/6d/0703ccc57f3a7233505399edb88de3cbd678da106337b9fcde432b65ed60/idna-3.11.tar.gz", hash = "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902", size = 194582 } +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/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea", size = 71008 }, + { 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 = "imagecodecs" version = "2026.3.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 == 'emscripten'", + "python_full_version < '3.12' and sys_platform != 'darwin' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version < '3.12' and sys_platform == 'darwin'", +] dependencies = [ - { name = "numpy" }, + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" } }, ] -sdist = { url = "https://files.pythonhosted.org/packages/3b/8d/dc18623e5e926ad53c626e128c8baaf4ec42e41029cf0a07381cfef79289/imagecodecs-2026.3.6.tar.gz", hash = "sha256:471b8a4d1b3843cbf7179b45f7d7261f0c0b28809efc1ca6c47822477b143b85", size = 9565259 } +sdist = { url = "https://files.pythonhosted.org/packages/3b/8d/dc18623e5e926ad53c626e128c8baaf4ec42e41029cf0a07381cfef79289/imagecodecs-2026.3.6.tar.gz", hash = "sha256:471b8a4d1b3843cbf7179b45f7d7261f0c0b28809efc1ca6c47822477b143b85", size = 9565259, upload-time = "2026-03-07T01:26:41.183Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/f3/db/873d063c99a726d772bf6f076288da59bb12e9f2af3518c2e4de5fde234d/imagecodecs-2026.3.6-cp311-abi3-macosx_10_15_x86_64.whl", hash = "sha256:44cfb3b609d941014f8ac7cf8611b15ccfd7119443bbb6b5e53916b242d31f9e", size = 13953250 }, - { url = "https://files.pythonhosted.org/packages/42/84/36c38a82f033ffbc9e706dad32be7148f130fc00e7bb417ab60e063897a0/imagecodecs-2026.3.6-cp311-abi3-macosx_11_0_arm64.whl", hash = "sha256:e64037f22980a211b17bf6bdf03f14ff459a7432eec24f7a58c342f6992132fa", size = 11697496 }, - { url = "https://files.pythonhosted.org/packages/45/fa/f67c4e644fdf06503e120f9d1c8d8654b99066dea7093a674b67704fa4a4/imagecodecs-2026.3.6-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:30fa140bb1a112a889926af36977214ed52a22e4557356043259b5e2f79cfba5", size = 25604431 }, - { url = "https://files.pythonhosted.org/packages/8f/29/93ea9cbab7f57b4e60480c51fc51d8e138e399d11797c981d5f6e79f9832/imagecodecs-2026.3.6-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:e30a14aa2e1c6c90e00375292726486c1d90bf003b1414d608ea4d1f62fd8a79", size = 26468592 }, - { url = "https://files.pythonhosted.org/packages/e8/a7/e3a89b2c516eaca7446e8f1335daeec90764b50888af5e073a2b6a987fcf/imagecodecs-2026.3.6-cp311-abi3-win32.whl", hash = "sha256:c972a45dfee1befbac048ba3492607003e9a185811e8febdc1ed531d48c07e75", size = 15597722 }, - { url = "https://files.pythonhosted.org/packages/22/c7/2b37a7fe9a2eb21011e50f046d62e68ac4e0f8d6ad94d7a10e9f8e8d685f/imagecodecs-2026.3.6-cp311-abi3-win_amd64.whl", hash = "sha256:e8fba5b9ac7be109ed35070208bc1683fa17cc381ed9535a4eae200c6d883bd8", size = 19177403 }, - { url = "https://files.pythonhosted.org/packages/c0/c7/94e930cef9e0a29a2df5e3ba3bacd2c2f1e34ca373fe48624b64af8ae91c/imagecodecs-2026.3.6-cp311-abi3-win_arm64.whl", hash = "sha256:fc4856913be6c8b3861223158920d934a0ae203149a435f585622dbbff8ed696", size = 15193605 }, - { url = "https://files.pythonhosted.org/packages/a3/0b/ba7ca5a14cf2ff744c47898ab9e98b6b96b364bce1459afcab5f4e5ea2bb/imagecodecs-2026.3.6-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:ea2cd774854a3bfe1dc9508f98907edc877c02a78642ab371adbdec65a8865cc", size = 14335958 }, - { url = "https://files.pythonhosted.org/packages/18/d9/4750fb9739d474e399fa566b3a5c7033f2c9c0078bc869473b23d3e0d4b7/imagecodecs-2026.3.6-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:6efa104ce600f61fef03a43daab34fe2953c8adb4e4ee6eb9544825f5361e5ab", size = 12021474 }, - { url = "https://files.pythonhosted.org/packages/29/d3/f0a71c797f836021241500d4a459b0f93317f2b5c9bcc39fb1eaa50e01ea/imagecodecs-2026.3.6-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:16fd83dd25d9c888317bc8cbbc5d7490b78c84c58275d80fb71ea7b28645276a", size = 29606531 }, - { url = "https://files.pythonhosted.org/packages/0c/8c/a1b433418306636fc22a94b669d7ebbbde8e3b26aef7d693c08f9f8a65af/imagecodecs-2026.3.6-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:39fcf671537cc92dd24ba4dcba257d8aa47e6f58018e839a5216c626f03d4126", size = 30039276 }, - { url = "https://files.pythonhosted.org/packages/ac/96/1474c0e242fb11d68304f858298971370fdfcecc404dbd9993a3ea449b4f/imagecodecs-2026.3.6-cp314-cp314t-win32.whl", hash = "sha256:65110ff30723a6e0b79c6c74ff52909a34b131366c4f76b1b68084cca5fcd982", size = 16345843 }, - { url = "https://files.pythonhosted.org/packages/d5/13/d0e09d8aaafe6fac1aa1bd8c0dcc0f024998ea4a23477128db14a085b4cf/imagecodecs-2026.3.6-cp314-cp314t-win_amd64.whl", hash = "sha256:1b810efeeb06bdcca07ae410a81bdb5eb9fb38f91689baf0f0b75783f5119a40", size = 20230890 }, - { url = "https://files.pythonhosted.org/packages/9c/06/3a5fe853e4df9a3655f8be81c1eb48013aab282c1557341b8634dd718e32/imagecodecs-2026.3.6-cp314-cp314t-win_arm64.whl", hash = "sha256:a2363c64e346cee4136f02a207b1b645729142f5260d122ddc3d5ff9e32ac6b1", size = 15911854 }, + { url = "https://files.pythonhosted.org/packages/f3/db/873d063c99a726d772bf6f076288da59bb12e9f2af3518c2e4de5fde234d/imagecodecs-2026.3.6-cp311-abi3-macosx_10_15_x86_64.whl", hash = "sha256:44cfb3b609d941014f8ac7cf8611b15ccfd7119443bbb6b5e53916b242d31f9e", size = 13953250, upload-time = "2026-03-07T06:13:36.959Z" }, + { url = "https://files.pythonhosted.org/packages/42/84/36c38a82f033ffbc9e706dad32be7148f130fc00e7bb417ab60e063897a0/imagecodecs-2026.3.6-cp311-abi3-macosx_11_0_arm64.whl", hash = "sha256:e64037f22980a211b17bf6bdf03f14ff459a7432eec24f7a58c342f6992132fa", size = 11697496, upload-time = "2026-03-07T01:25:56.412Z" }, + { url = "https://files.pythonhosted.org/packages/45/fa/f67c4e644fdf06503e120f9d1c8d8654b99066dea7093a674b67704fa4a4/imagecodecs-2026.3.6-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:30fa140bb1a112a889926af36977214ed52a22e4557356043259b5e2f79cfba5", size = 25604431, upload-time = "2026-03-07T01:26:00.703Z" }, + { url = "https://files.pythonhosted.org/packages/8f/29/93ea9cbab7f57b4e60480c51fc51d8e138e399d11797c981d5f6e79f9832/imagecodecs-2026.3.6-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:e30a14aa2e1c6c90e00375292726486c1d90bf003b1414d608ea4d1f62fd8a79", size = 26468592, upload-time = "2026-03-07T01:26:04.933Z" }, + { url = "https://files.pythonhosted.org/packages/e8/a7/e3a89b2c516eaca7446e8f1335daeec90764b50888af5e073a2b6a987fcf/imagecodecs-2026.3.6-cp311-abi3-win32.whl", hash = "sha256:c972a45dfee1befbac048ba3492607003e9a185811e8febdc1ed531d48c07e75", size = 15597722, upload-time = "2026-03-07T01:26:08.106Z" }, + { url = "https://files.pythonhosted.org/packages/22/c7/2b37a7fe9a2eb21011e50f046d62e68ac4e0f8d6ad94d7a10e9f8e8d685f/imagecodecs-2026.3.6-cp311-abi3-win_amd64.whl", hash = "sha256:e8fba5b9ac7be109ed35070208bc1683fa17cc381ed9535a4eae200c6d883bd8", size = 19177403, upload-time = "2026-03-07T01:26:11.718Z" }, + { url = "https://files.pythonhosted.org/packages/c0/c7/94e930cef9e0a29a2df5e3ba3bacd2c2f1e34ca373fe48624b64af8ae91c/imagecodecs-2026.3.6-cp311-abi3-win_arm64.whl", hash = "sha256:fc4856913be6c8b3861223158920d934a0ae203149a435f585622dbbff8ed696", size = 15193605, upload-time = "2026-03-07T01:26:15.016Z" }, +] + +[[package]] +name = "imagecodecs" +version = "2026.6.26" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.15' and sys_platform == 'win32'", + "python_full_version == '3.14.*' and sys_platform == 'win32'", + "python_full_version >= '3.15' and sys_platform == 'emscripten'", + "python_full_version == '3.14.*' and sys_platform == 'emscripten'", + "python_full_version >= '3.15' and sys_platform != 'darwin' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.14.*' and sys_platform != 'darwin' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version >= '3.15' and sys_platform == 'darwin'", + "python_full_version == '3.14.*' and sys_platform == 'darwin'", + "python_full_version == '3.13.*' and sys_platform == 'win32'", + "python_full_version == '3.12.*' and sys_platform == 'win32'", + "python_full_version == '3.13.*' and sys_platform == 'emscripten'", + "python_full_version == '3.12.*' and sys_platform == 'emscripten'", + "python_full_version == '3.13.*' and sys_platform != 'darwin' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.12.*' and sys_platform != 'darwin' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.13.*' and sys_platform == 'darwin'", + "python_full_version == '3.12.*' and sys_platform == 'darwin'", +] +dependencies = [ + { name = "numpy", version = "2.5.1", source = { registry = "https://pypi.org/simple" } }, +] +sdist = { url = "https://files.pythonhosted.org/packages/98/9f/b436418349779e1c18cc27575360cad03dd492bc574ae9e297832bc48cab/imagecodecs-2026.6.26.tar.gz", hash = "sha256:da95b145f6b4f746acc9e0b8707b164eefc6a36ade3b6e70f74d102e9affad8c", size = 9669990, upload-time = "2026-06-28T18:27:01.957Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7e/03/89bf1a370dca9860b13414f63d7106600c527305d9d8c0369c15a93ca794/imagecodecs-2026.6.26-cp312-abi3-macosx_10_15_x86_64.whl", hash = "sha256:468314982db54c9c6c32567b159de5d97cf5273763cdede53e320414787ad80a", size = 15387254, upload-time = "2026-06-28T18:26:00.528Z" }, + { url = "https://files.pythonhosted.org/packages/05/47/826d50cd4378a6982d8a750300b9adc81c3458c4c1bdfd979affc8ce5492/imagecodecs-2026.6.26-cp312-abi3-macosx_11_0_arm64.whl", hash = "sha256:2d3298028a74d748e5b7a00bd736d41cdf2372861376e4af916818e853ca5fc6", size = 12696122, upload-time = "2026-06-28T18:26:03.714Z" }, + { url = "https://files.pythonhosted.org/packages/c6/52/ccd8a8a71bb2d0ed9fd4a2592028ab129f6b41c6f2b0ef6c1b9251ec7c88/imagecodecs-2026.6.26-cp312-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:73b4310cab61edf5c74ec5cb19672963c2ceb42239877256ca30eb1b70849cf3", size = 27215507, upload-time = "2026-06-28T18:26:07.479Z" }, + { url = "https://files.pythonhosted.org/packages/d8/3a/29fa169992185df4f1e98b3a1d2b70aac5903170c33f6d66cae68f488c01/imagecodecs-2026.6.26-cp312-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:6e9e57171ce7cdf884e7d45d25427f77275321a0ddbd26941c5c90b242d2b597", size = 28855566, upload-time = "2026-06-28T18:26:11.7Z" }, + { url = "https://files.pythonhosted.org/packages/29/c7/27d82b105d871ceb6de3a68afbee5f95551b8fba796e4e6e35e334f994f4/imagecodecs-2026.6.26-cp312-abi3-win32.whl", hash = "sha256:091b66cfe11e37e5a037ff64b262bede3898f779ebd9cbf0d4a108613577ab15", size = 16568720, upload-time = "2026-06-28T18:26:15.259Z" }, + { url = "https://files.pythonhosted.org/packages/c8/e2/8d27dbe855293380194aa794cbcce1df2d23039058b3d917817aaa3f5547/imagecodecs-2026.6.26-cp312-abi3-win_amd64.whl", hash = "sha256:0758a273acd197e236e67f1597d630924f62c12f5b74837dc7eef2db72c1a888", size = 21275600, upload-time = "2026-06-28T18:26:19.678Z" }, + { url = "https://files.pythonhosted.org/packages/ab/38/3d0d0a4abc3ba607f23a3237a1cb6f7737dbd1b39498364f01f62c004e8b/imagecodecs-2026.6.26-cp312-abi3-win_arm64.whl", hash = "sha256:8bbf132cce699c6b282106e97edc65946d788ccca79f6ba8f5cd1b8a3f43423c", size = 16849832, upload-time = "2026-06-28T18:26:22.797Z" }, + { url = "https://files.pythonhosted.org/packages/f4/30/040baf3050ee9803608ff9694a4a4dc9ec868e222fe4c5c8de481388ffb0/imagecodecs-2026.6.26-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:46980597f48fe790f81e286cf662f0323d0022203cbe48f77c3be608f510de85", size = 15803759, upload-time = "2026-06-28T18:26:25.873Z" }, + { url = "https://files.pythonhosted.org/packages/ff/e8/042eeac7c78565f1b4c14ba3dc6a96aad9b958e008a1e291a1cbf72123e9/imagecodecs-2026.6.26-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c48767bbc6ed0b40df3607cafdf59dac6f4467b91c26e835744d7ee4085cfdf7", size = 13164944, upload-time = "2026-06-28T18:26:29.33Z" }, + { url = "https://files.pythonhosted.org/packages/1d/dd/0e0c86df9a55a0a90ced8cf5214abb459015cda5f2e713e0495ccd9bf23f/imagecodecs-2026.6.26-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:9c130cf207efcb35acf74d6040317d217294640e478aa733fb51be758bcaddbc", size = 32436234, upload-time = "2026-06-28T18:26:33.35Z" }, + { url = "https://files.pythonhosted.org/packages/b9/9a/2d63eb2ffa46863bf0ea05884ce1ea2a73a5153b428937b8cbe4faa81536/imagecodecs-2026.6.26-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:014f5f809418eb3dfe29badd4af29ddeeaba8b5eabda4bd502e2292727e4094f", size = 33446845, upload-time = "2026-06-28T18:26:38.238Z" }, + { url = "https://files.pythonhosted.org/packages/14/68/ece973cd5b1d8305b95ad827b3c6033aed166c5ce93780b6092dbfd5deec/imagecodecs-2026.6.26-cp314-cp314t-win32.whl", hash = "sha256:daa276e5645750404a28a911dcbfb94c7d32b2d8903b5f05167474ea02bd5b40", size = 17330378, upload-time = "2026-06-28T18:26:42.172Z" }, + { url = "https://files.pythonhosted.org/packages/ad/e6/ab2e9ee84e44768ceb73bb9301988ea4ab9022f02ef9d824d6ebb641a0fb/imagecodecs-2026.6.26-cp314-cp314t-win_amd64.whl", hash = "sha256:0718d75672768871aa6adac2129c8f74c167fb4a3da25f660f3afc94b420304f", size = 22122461, upload-time = "2026-06-28T18:26:45.622Z" }, + { url = "https://files.pythonhosted.org/packages/58/53/7943321eb84c6d39e71ca47e689557a976c6ebafec48b759b3be0bd7c601/imagecodecs-2026.6.26-cp314-cp314t-win_arm64.whl", hash = "sha256:69b90b7b729d33cd9417d3d572d3092d2d49f7974638ee00e2cdc3ed1fba11d6", size = 17657147, upload-time = "2026-06-28T18:26:48.924Z" }, + { url = "https://files.pythonhosted.org/packages/f8/de/130820754c21c00dfdc6852661de2bb30161b5a439a8643a446dae97a02f/imagecodecs-2026.6.26-cp315-cp315t-win32.whl", hash = "sha256:a10c347225f5fb1b9d1a36cfadccad9b173609c30e0dd3f68706675d6f4f9b58", size = 17330338, upload-time = "2026-06-28T18:26:52.04Z" }, + { url = "https://files.pythonhosted.org/packages/a7/df/b4f6f2a9e9102ca674673cb1b432bbebfe2d99acb6364390768024d56ae6/imagecodecs-2026.6.26-cp315-cp315t-win_amd64.whl", hash = "sha256:85ab027a8bc900f13b1cdaac05bb602ea58749731ae18743a7956642a7da7a2c", size = 22116857, upload-time = "2026-06-28T18:26:55.65Z" }, + { url = "https://files.pythonhosted.org/packages/2e/c4/d87b33e8df5c6ec19f5cd1d5ab39195b2077ac21536ff5146c20d3fb8694/imagecodecs-2026.6.26-cp315-cp315t-win_arm64.whl", hash = "sha256:41e18fcd2124c083b00f988eeb9c5cf89274e5c2f473ee03d54761b71048085c", size = 17651071, upload-time = "2026-06-28T18:26:59.305Z" }, ] [[package]] name = "imageio" -version = "2.37.3" +version = "2.37.4" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "numpy" }, + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, + { name = "numpy", version = "2.5.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, { name = "pillow" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/b1/84/93bcd1300216ea50811cee96873b84a1bebf8d0489ffaf7f2a3756bab866/imageio-2.37.3.tar.gz", hash = "sha256:bbb37efbfc4c400fcd534b367b91fcd66d5da639aaa138034431a1c5e0a41451", size = 389673 } +sdist = { url = "https://files.pythonhosted.org/packages/48/62/aa770a9307508d2a2a2c62d536a49347bffe9e55322db27838d3c93d0b07/imageio-2.37.4.tar.gz", hash = "sha256:e45cbc5e83502047fb138f7f585f7f105a136a57eea5f4b3cfc6ce1b52720bd3", size = 390173, upload-time = "2026-07-20T05:26:11.369Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/49/fa/391e437a34e55095173dca5f24070d89cbc233ff85bf1c29c93248c6588d/imageio-2.37.3-py3-none-any.whl", hash = "sha256:46f5bb8522cd421c0f5ae104d8268f569d856b29eb1a13b92829d1970f32c9f0", size = 317646 }, + { url = "https://files.pythonhosted.org/packages/3e/2d/ca050652104bab2cf55e569db2a178b1b61cb041fef28307f2db383f6d9f/imageio-2.37.4-py3-none-any.whl", hash = "sha256:1ab2e22c8debf700f24c3ac43e8f95f3b3a8110c83b93411e97b4b0b2cd1c7e6", size = 318000, upload-time = "2026-07-20T05:26:09.874Z" }, ] [[package]] @@ -609,17 +713,22 @@ name = "imread-benchmark" version = "0.2.0" source = { editable = "." } dependencies = [ - { name = "numpy" }, + { name = "jsonschema" }, + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, + { name = "numpy", version = "2.5.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, { name = "pandas" }, { name = "py-cpuinfo" }, + { name = "pyyaml" }, { name = "tqdm" }, { name = "typer" }, + { name = "zstandard" }, ] [package.optional-dependencies] mainstream = [ { name = "ajpegli" }, - { name = "imagecodecs" }, + { name = "imagecodecs", version = "2026.3.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, + { name = "imagecodecs", version = "2026.6.26", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, { name = "imageio" }, { name = "jpeg4py", marker = "sys_platform == 'linux'" }, { name = "kornia-rs" }, @@ -630,10 +739,10 @@ mainstream = [ { name = "pyvips-binary" }, { name = "scikit-image" }, { name = "simplejpeg" }, - { name = "torch", version = "2.11.0", source = { registry = "https://download.pytorch.org/whl/cpu" }, marker = "sys_platform == 'darwin'" }, - { name = "torch", version = "2.11.0+cpu", source = { registry = "https://download.pytorch.org/whl/cpu" }, marker = "sys_platform != 'darwin'" }, - { name = "torchvision", version = "0.26.0", source = { registry = "https://download.pytorch.org/whl/cpu" }, marker = "sys_platform == 'darwin'" }, - { name = "torchvision", version = "0.26.0+cpu", source = { registry = "https://download.pytorch.org/whl/cpu" }, marker = "sys_platform != 'darwin'" }, + { name = "torch", version = "2.13.0", source = { registry = "https://download.pytorch.org/whl/cpu" }, marker = "sys_platform == 'darwin'" }, + { name = "torch", version = "2.13.0+cpu", source = { registry = "https://download.pytorch.org/whl/cpu" }, marker = "sys_platform != 'darwin'" }, + { name = "torchvision", version = "0.28.0", source = { registry = "https://download.pytorch.org/whl/cpu" }, marker = "sys_platform == 'darwin'" }, + { name = "torchvision", version = "0.28.0+cpu", source = { registry = "https://download.pytorch.org/whl/cpu" }, marker = "sys_platform != 'darwin'" }, ] plot = [ { name = "matplotlib" }, @@ -645,9 +754,11 @@ tensorflow = [ [package.dev-dependencies] dev = [ + { name = "mypy" }, { name = "pillow" }, { name = "pre-commit" }, { name = "pytest" }, + { name = "types-pyyaml" }, ] [package.metadata] @@ -656,6 +767,7 @@ requires-dist = [ { name = "imagecodecs", marker = "extra == 'mainstream'" }, { name = "imageio", marker = "extra == 'mainstream'" }, { name = "jpeg4py", marker = "sys_platform == 'linux' and extra == 'mainstream'" }, + { name = "jsonschema", specifier = ">=4.23" }, { name = "kornia-rs", marker = "extra == 'mainstream'" }, { name = "matplotlib", marker = "extra == 'plot'", specifier = ">=3.8" }, { name = "numpy" }, @@ -663,9 +775,10 @@ requires-dist = [ { name = "pandas" }, { name = "pillow", marker = "extra == 'mainstream'" }, { name = "py-cpuinfo" }, - { name = "pyturbojpeg", marker = "extra == 'mainstream'", specifier = "<2" }, + { name = "pyturbojpeg", marker = "extra == 'mainstream'" }, { name = "pyvips", marker = "extra == 'mainstream'" }, { name = "pyvips-binary", marker = "extra == 'mainstream'", specifier = ">=8.18" }, + { name = "pyyaml", specifier = ">=6" }, { name = "scikit-image", marker = "extra == 'mainstream'" }, { name = "seaborn", marker = "extra == 'plot'", specifier = ">=0.13" }, { name = "simplejpeg", marker = "extra == 'mainstream'" }, @@ -674,23 +787,26 @@ requires-dist = [ { name = "torchvision", marker = "extra == 'mainstream'", index = "https://download.pytorch.org/whl/cpu" }, { name = "tqdm" }, { name = "typer", specifier = ">=0.12" }, + { name = "zstandard", specifier = ">=0.23" }, ] provides-extras = ["mainstream", "plot", "tensorflow"] [package.metadata.requires-dev] dev = [ + { name = "mypy", specifier = ">=1.20" }, { name = "pillow" }, { name = "pre-commit", specifier = ">=3.5" }, { name = "pytest", specifier = ">=8" }, + { name = "types-pyyaml", specifier = ">=6.0.12" }, ] [[package]] name = "iniconfig" version = "2.3.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503 } +sdist = { url = "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503, upload-time = "2025-10-18T21:55:43.219Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484 }, + { url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" }, ] [[package]] @@ -700,9 +816,9 @@ 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 } +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 }, + { 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]] @@ -710,168 +826,201 @@ name = "jpeg4py" version = "0.1.4" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "cffi", marker = "sys_platform != 'darwin' and sys_platform != 'emscripten' and sys_platform != 'win32'" }, - { name = "numpy", marker = "sys_platform != 'darwin' and sys_platform != 'emscripten' and sys_platform != 'win32'" }, + { name = "cffi" }, + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, + { name = "numpy", version = "2.5.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b4/b7/d06702a6f2fe907f8e1eb337334163f045c833db6710fb3acf47ca2460ee/jpeg4py-0.1.4.tar.gz", hash = "sha256:cec3adcb110bd1391659670f2fe64d2a70a0218e84fdb7b08506107946d3bdb0", size = 12009, upload-time = "2018-02-23T02:44:50.905Z" } + +[[package]] +name = "jsonschema" +version = "4.26.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "attrs" }, + { name = "jsonschema-specifications" }, + { name = "referencing" }, + { name = "rpds-py" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b3/fc/e067678238fa451312d4c62bf6e6cf5ec56375422aee02f9cb5f909b3047/jsonschema-4.26.0.tar.gz", hash = "sha256:0c26707e2efad8aa1bfc5b7ce170f3fccc2e4918ff85989ba9ffa9facb2be326", size = 366583, upload-time = "2026-01-07T13:41:07.246Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/69/90/f63fb5873511e014207a475e2bb4e8b2e570d655b00ac19a9a0ca0a385ee/jsonschema-4.26.0-py3-none-any.whl", hash = "sha256:d489f15263b8d200f8387e64b4c3a75f06629559fb73deb8fdfb525f2dab50ce", size = 90630, upload-time = "2026-01-07T13:41:05.306Z" }, +] + +[[package]] +name = "jsonschema-specifications" +version = "2025.9.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "referencing" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/19/74/a633ee74eb36c44aa6d1095e7cc5569bebf04342ee146178e2d36600708b/jsonschema_specifications-2025.9.1.tar.gz", hash = "sha256:b540987f239e745613c7a9176f3edb72b832a4ac465cf02712288397832b5e8d", size = 32855, upload-time = "2025-09-08T01:34:59.186Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/41/45/1a4ed80516f02155c51f51e8cedb3c1902296743db0bbc66608a0db2814f/jsonschema_specifications-2025.9.1-py3-none-any.whl", hash = "sha256:98802fee3a11ee76ecaca44429fda8a41bff98b00a0f2838151b113f210cc6fe", size = 18437, upload-time = "2025-09-08T01:34:57.871Z" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/b4/b7/d06702a6f2fe907f8e1eb337334163f045c833db6710fb3acf47ca2460ee/jpeg4py-0.1.4.tar.gz", hash = "sha256:cec3adcb110bd1391659670f2fe64d2a70a0218e84fdb7b08506107946d3bdb0", size = 12009 } [[package]] name = "keras" -version = "3.14.0" +version = "3.15.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "absl-py" }, { name = "h5py" }, { name = "ml-dtypes" }, { name = "namex" }, - { name = "numpy" }, + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, + { name = "numpy", version = "2.5.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, { name = "optree" }, { name = "packaging" }, { name = "rich" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/88/ce/47874047a49eedc2a5d3b41bc4f1f572bb637f51e4351ef3538e49a63800/keras-3.14.0.tar.gz", hash = "sha256:86fcf8249a25264a566ac393c287c7ad657000e5e62615dcaad4b3472a17aeda", size = 1263098 } +sdist = { url = "https://files.pythonhosted.org/packages/ec/a6/4cebb4196f4e6284b35e68dc17dda9a4111a2e2bf21db8211de12881ef89/keras-3.15.1.tar.gz", hash = "sha256:92ae7c1dd7f61041953dc2d42253181ee099086f0b58ae36fcf98147a6f08a29", size = 1919818, upload-time = "2026-07-29T18:20:13.909Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c0/20/78d26f81115d570bdf0e57d19b81de9ad8aa55ddb68eb10c8f0699fccb63/keras-3.14.0-py3-none-any.whl", hash = "sha256:19ce94b798caaba4d404ab6ef4753b44219170e5c2868156de8bb0494a260114", size = 1627362 }, + { url = "https://files.pythonhosted.org/packages/8d/b3/c9b848bbdba18e765a8051917c0cc82585b64278ce87895f2e521a27438f/keras-3.15.1-py3-none-any.whl", hash = "sha256:836460e480930acbd19bb7a17e62f9ecad40e8a9af9a651fc8d0586a96d27e20", size = 2398595, upload-time = "2026-07-29T18:20:12.345Z" }, ] [[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 } -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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { url = "https://files.pythonhosted.org/packages/be/6c/28f17390b62b8f2f520e2915095b3c94d88681ecf0041e75389d9667f202/kiwisolver-1.5.0-cp311-cp311-win_amd64.whl", hash = "sha256:beb7f344487cdcb9e1efe4b7a29681b74d34c08f0043a327a74da852a6749e7b", size = 73480 }, - { url = "https://files.pythonhosted.org/packages/d8/0e/2ee5debc4f77a625778fec5501ff3e8036fe361b7ee28ae402a485bb9694/kiwisolver-1.5.0-cp311-cp311-win_arm64.whl", hash = "sha256:ad4ae4ffd1ee9cd11357b4c66b612da9888f4f4daf2f36995eda64bd45370cac", size = 64930 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { url = "https://files.pythonhosted.org/packages/ad/cf/0348374369ca588f8fe9c338fae49fa4e16eeb10ffb3d012f23a54578a9e/kiwisolver-1.5.0-cp312-cp312-win_amd64.whl", hash = "sha256:f18c2d9782259a6dc132fdc7a63c168cbc74b35284b6d75c673958982a378384", size = 73569 }, - { url = "https://files.pythonhosted.org/packages/28/26/192b26196e2316e2bd29deef67e37cdf9870d9af8e085e521afff0fed526/kiwisolver-1.5.0-cp312-cp312-win_arm64.whl", hash = "sha256:f7c7553b13f69c1b29a5bde08ddc6d9d0c8bfb84f9ed01c30db25944aeb852a7", size = 64997 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { url = "https://files.pythonhosted.org/packages/be/8a/be60e3bbcf513cc5a50f4a3e88e1dcecebb79c1ad607a7222877becaa101/kiwisolver-1.5.0-cp313-cp313-win_amd64.whl", hash = "sha256:0bf3acf1419fa93064a4c2189ac0b58e3be7872bf6ee6177b0d4c63dc4cea276", size = 73573 }, - { url = "https://files.pythonhosted.org/packages/4d/d2/64be2e429eb4fca7f7e1c52a91b12663aeaf25de3895e5cca0f47ef2a8d0/kiwisolver-1.5.0-cp313-cp313-win_arm64.whl", hash = "sha256:fa8eb9ecdb7efb0b226acec134e0d709e87a909fa4971a54c0c4f6e88635484c", size = 64998 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { url = "https://files.pythonhosted.org/packages/47/84/6a6d5e5bb8273756c27b7d810d47f7ef2f1f9b9fd23c9ee9a3f8c75c9cef/kiwisolver-1.5.0-cp313-cp313t-win_arm64.whl", hash = "sha256:893ff3a711d1b515ba9da14ee090519bad4610ed1962fbe298a434e8c5f8db53", size = 68410 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { url = "https://files.pythonhosted.org/packages/a3/36/4e551e8aa55c9188bca9abb5096805edbf7431072b76e2298e34fd3a3008/kiwisolver-1.5.0-cp314-cp314-win_amd64.whl", hash = "sha256:d76e2d8c75051d58177e762164d2e9ab92886534e3a12e795f103524f221dd8e", size = 75086 }, - { url = "https://files.pythonhosted.org/packages/70/15/9b90f7df0e31a003c71649cf66ef61c3c1b862f48c81007fa2383c8bd8d7/kiwisolver-1.5.0-cp314-cp314-win_arm64.whl", hash = "sha256:fa6248cd194edff41d7ea9425ced8ca3a6f838bfb295f6f1d6e6bb694a8518df", size = 66577 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { url = "https://files.pythonhosted.org/packages/3b/b5/98222136d839b8afabcaa943b09bd05888c2d36355b7e448550211d1fca4/kiwisolver-1.5.0-cp314-cp314t-win_amd64.whl", hash = "sha256:1dd9b0b119a350976a6d781e7278ec7aca0b201e1a9e2d23d9804afecb6ca681", size = 79687 }, - { url = "https://files.pythonhosted.org/packages/99/a2/ca7dc962848040befed12732dff6acae7fb3c4f6fc4272b3f6c9a30b8713/kiwisolver-1.5.0-cp314-cp314t-win_arm64.whl", hash = "sha256:58f812017cd2985c21fbffb4864d59174d4903dd66fa23815e74bbc7a0e2dd57", size = 70032 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { url = "https://files.pythonhosted.org/packages/0a/dd/8050c947d435c8d4bc94e3252f4d8bb8a76cfb424f043a8680be637a57f1/kiwisolver-1.5.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:59cd8683f575d96df5bb48f6add94afc055012c29e28124fcae2b63661b9efb1", size = 73558 }, +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 = "kornia-rs" -version = "0.1.10" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ab/17/8b3518ece01512a575b18f86b346879793d3dea264b314796bbd44d42e11/kornia_rs-0.1.10.tar.gz", hash = "sha256:5fd3fbc65240fa751975f5870b079f98e7fdcaa2885ea577b3da324d8bf01d81", size = 145610 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/20/25/ab91a87cefd8d92a10749fa5d923366dfd2a2d240d9e57260e4218e9a5af/kornia_rs-0.1.10-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:6757940733f13c52c4f142b9b11e3e9bd12ef9d209e333300602e86e21f5ae2f", size = 2811949 }, - { url = "https://files.pythonhosted.org/packages/ae/61/6125a970249e04dd31cf3edf3fb0ceb98ea65269bc416ba48fd70f9a8f5e/kornia_rs-0.1.10-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:68e90101a34ba2bbce920332b25fd4d25c8c546d9a241b2606a6d886df2dd1ed", size = 2078639 }, - { url = "https://files.pythonhosted.org/packages/0a/e4/c3484e5921a08e6368f0565c30646741fd12b46cb45c962d519cac3d12ad/kornia_rs-0.1.10-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6b0adb81858a8963455f2f0da01fcd6ea3296147b918306488edeeaf6bc2a979", size = 2204722 }, - { url = "https://files.pythonhosted.org/packages/93/a4/2e6e33da900f19ae6411bfad41d317e56f1ae4f204bd73e61f0881bd5418/kornia_rs-0.1.10-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0c3e237a8428524ad9f86599c0c47b355bc3007669fe297ea3fbd59cd64bc2f7", size = 3042890 }, - { url = "https://files.pythonhosted.org/packages/40/48/5e171c98b742139bebd1bd593d768e3c045f824bf0ae14190b63f0ac0acc/kornia_rs-0.1.10-cp311-cp311-win_amd64.whl", hash = "sha256:1d300ea6d4666e47302fba6cc438556d91e37ce41caf291a9a04a8f74c231d0b", size = 2544572 }, - { url = "https://files.pythonhosted.org/packages/d8/6c/8248f08c90a10d6b8ca2e74783da8df7fa509f46b64a3b4fbb7dd0ac4e9c/kornia_rs-0.1.10-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:f0809277e51156d59be3c39605ba9659e94f7a4cf3b0b6c035ec2f06f6067881", size = 2811606 }, - { url = "https://files.pythonhosted.org/packages/83/dc/29e5710cbc5d01c155ee1fd7621db48b94378a7ae394741bb34a6bfb36d9/kornia_rs-0.1.10-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8ecf2ba0291cc1bb178073d56e46b16296a8864a20272b63af02ee88771cb574", size = 2076141 }, - { url = "https://files.pythonhosted.org/packages/68/f7/0b3e90b9d0a25e6211c7ac9fa1dfed4db1306a812c359ee49678390a1bdc/kornia_rs-0.1.10-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d874ca12dd58871f9849672d9bf9fa998398470a88b52d61223ce2133b196662", size = 2205562 }, - { url = "https://files.pythonhosted.org/packages/63/d4/315f358b2a2c29d9af3a73f3d1973c2fd8e0cdeb65a57af98643e66fa7c8/kornia_rs-0.1.10-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f332a2a034cc791006f25c2d85e342a060887145e9236e8e43562badcadededf", size = 3042197 }, - { url = "https://files.pythonhosted.org/packages/d3/b8/0ddbdf1d35fec3ef24f5b8cc29eb633ce5ce16c94c9fb090408c1280abe9/kornia_rs-0.1.10-cp312-cp312-win_amd64.whl", hash = "sha256:34111ce1c8abe930079b4b0aeb8d372f876c621a867ed03f77181de685e71a8f", size = 2539656 }, - { url = "https://files.pythonhosted.org/packages/90/01/1d658b11635431f8c31f416c90ca99befdc1f4fdd20e91a05b480b9c0ea8/kornia_rs-0.1.10-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:950a943f91c2cff94d80282886b0d48bbc15ef4a7cc4b15ac819724dfdb2f414", size = 2811810 }, - { url = "https://files.pythonhosted.org/packages/e4/ed/bd970ded1d819557cc33055d982b1847eb385151ea5b0c915c16ed74f5c0/kornia_rs-0.1.10-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:63b802aaf95590276d3426edc6d23ff11caf269d2bc2ec37cb6c679b7b2a8ee0", size = 2076195 }, - { url = "https://files.pythonhosted.org/packages/c1/10/afd700455105fdba5b043d724f3a65ca36259b89c736a3b71d5a03103808/kornia_rs-0.1.10-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:38087da7cdf2bffe10530c0d53335dd1fc107fae6521f2dd4797c6522b6d11b3", size = 2205781 }, - { url = "https://files.pythonhosted.org/packages/25/16/ec8dc3ce1d79660ddd6a186a77037e0c3bf61648e6c72250280b648fb291/kornia_rs-0.1.10-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fa3464de8f9920d87415721c36840ceea23e054dcb54dd9f69189ba9eabce0c7", size = 3042272 }, - { url = "https://files.pythonhosted.org/packages/f7/75/62785aba777d35a562a97a987d65840306fab7a8ecd2d928dd8ac779e29b/kornia_rs-0.1.10-cp313-cp313-win_amd64.whl", hash = "sha256:c57d157bebe64c22e2e44c72455b1c7365eee4d767e0c187dc28f22d072ebaf7", size = 2539802 }, - { url = "https://files.pythonhosted.org/packages/a5/d5/32b23d110109eb77b2dc952be75411f7e495da9105058e2cb08924a9cc90/kornia_rs-0.1.10-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:0b375f02422ef5986caed612799b4ddcc91f57f303906868b0a8c397a17e7607", size = 2810244 }, - { url = "https://files.pythonhosted.org/packages/96/5f/5ecde42b7c18e7df26c413848a98744427c3d370f5eed725b65f0bc356fb/kornia_rs-0.1.10-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:f2bcfa438d6b5dbe07d573afc980f2871f6639b2eac5148b8c0bba4f82357b9a", size = 2074220 }, - { url = "https://files.pythonhosted.org/packages/18/6c/6fc86eb855bcc723924c3b91de98dc6c0f381987ce582e080b8eade3bc88/kornia_rs-0.1.10-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:021b0a02b2356b12b3954a298f369ed4fe2dd522dcf8b6d72f91bf3bd8eea201", size = 2204672 }, - { url = "https://files.pythonhosted.org/packages/19/26/3ac706d1b36761c0f7a36934327079adcb42d761c8c219865123d49fc1b2/kornia_rs-0.1.10-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8d9b07e2ae79e423b3248d94afd092e324c5ddfe3157fafc047531cc8bffa6a3", size = 3042797 }, - { url = "https://files.pythonhosted.org/packages/8d/f4/d62728d86bc67f5516249b154ff0bdfcf38a854dae284ff0ce62da87af99/kornia_rs-0.1.10-cp313-cp313t-win_amd64.whl", hash = "sha256:b80a037e34d63cb021bcd5fc571e41aff804a2981311f66e883768c6b8e5f8de", size = 2543855 }, - { url = "https://files.pythonhosted.org/packages/91/d5/8ed1288a51d2ad71a6c01152ceccdd2d92f21692dfd2304b1ae9383496fa/kornia_rs-0.1.10-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:119eb434d1384257cae6c1ee9444e1aa1b0fda617f6d5a79fef3f145fdac70ac", size = 2809873 }, - { url = "https://files.pythonhosted.org/packages/54/2b/fd5f919723aaa69ec5c1e60b10b7904a9126be5b9d6ccc0267fa42ca77e0/kornia_rs-0.1.10-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:60bca692911e5969e51d256299ecc6e90d32b9a2c5bf7bd1c7eb8f096cb9234b", size = 2074360 }, - { url = "https://files.pythonhosted.org/packages/43/ec/7987aa5fb7d188180866bd8dafa5bb5b1f00a74ba738bb4e2abe63c589ac/kornia_rs-0.1.10-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:61f126644f49ff9947d9402126edacfeeb4b47c0999a7af487d27ce4fc4cbc2a", size = 2206111 }, - { url = "https://files.pythonhosted.org/packages/91/08/cb73b7e87a07b2af1146988d159d48722f0a28f550f920397c8964ab7c19/kornia_rs-0.1.10-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:614aeffdb1d39c4041ace0e0fc318b36eb211f6c133912984e946174e67dbb42", size = 3041436 }, - { url = "https://files.pythonhosted.org/packages/db/e2/9f50fce2d8e9edd6b2d09908b6d5613f9ead992bf2e80060e080f2e7d64d/kornia_rs-0.1.10-cp314-cp314-win_amd64.whl", hash = "sha256:6de4e73b1c90cc76b7486491866eb9e61e5cf34d3a4016957d4563ac7d3ee39a", size = 2544067 }, - { url = "https://files.pythonhosted.org/packages/d1/8f/45895818f3c7a5009737119b075db6b88bbf00938275611bc5d2cfbd0b2a/kornia_rs-0.1.10-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:f0db8b41ae03a746bb0dcb970d5ff2fd66213adb4a3b4de1186fe86205698e89", size = 2806089 }, - { url = "https://files.pythonhosted.org/packages/38/af/831e79b45702f8b6102438b1ff9b44a912669890cdf209cd275257f6d655/kornia_rs-0.1.10-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:9b63ee175125892ef18027bd3a43b447fd53f9bf42cea4d6f699ab4e69cf3f16", size = 2064116 }, - { url = "https://files.pythonhosted.org/packages/53/1b/e92606e0fa9a1b52ecf57faf322dcc076ae35315b4e1870d380fd64926d7/kornia_rs-0.1.10-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:68eb25ba4639fa5e1cd94a10fb6410c8840c9f0162e5912d834c4a8c7c174493", size = 2197890 }, - { url = "https://files.pythonhosted.org/packages/2e/fa/a2adce992b5eb65ef8adfc6f4465989948bfa8b875638e17c214541af25a/kornia_rs-0.1.10-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dc18ba839f5c10ceb4757342ee7530cef8a0ecdd20486b8bbe14a56f72fa7037", size = 3040852 }, - { url = "https://files.pythonhosted.org/packages/ee/36/40a3e3a235c370f5f61a8f9a7bdedf47d1bdd8f7d7e145e551545babff6b/kornia_rs-0.1.10-cp314-cp314t-win_amd64.whl", hash = "sha256:257eb0a780f990c0c44ac47acb77504dd95b8df0c592fd31354da1228df6678d", size = 2543609 }, +version = "0.1.14" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/09/0f/cd6985790031ad2f0d4fcdfdb9763a177bebb970edddc6fe29f9e6afd902/kornia_rs-0.1.14.tar.gz", hash = "sha256:7584f654a9db2b41bee05c9aaf865608b665e2f7195096372e001b6f220de1d2", size = 2556658, upload-time = "2026-05-19T07:45:06.366Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a3/4c/65769abe80221b32943399281c35dac2f8789640f86b002ed88a4235c908/kornia_rs-0.1.14-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:378ea4dd5aa82a8d754d48713da4f6794ceacc6fe6e429aead9095a75faff01c", size = 3556279, upload-time = "2026-05-19T07:46:00.181Z" }, + { url = "https://files.pythonhosted.org/packages/a0/48/8d68671fa7e1333f782016a01410be71cb796c3bcc844498e2c2bd6f1c12/kornia_rs-0.1.14-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e9d694526266252418084dca90814753eec43ff0194557b7824334c1e49bb9eb", size = 3351592, upload-time = "2026-05-19T07:45:45.894Z" }, + { url = "https://files.pythonhosted.org/packages/78/e1/62e3968111482ccd64aba1f521de2decc904078e624afcc368c77f012d9c/kornia_rs-0.1.14-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7ecf81b642e6f770e2212a888935c18dfcc8cf00e65474262e77b5acf5409648", size = 3385010, upload-time = "2026-05-19T07:45:08.352Z" }, + { url = "https://files.pythonhosted.org/packages/0c/da/18bbc67c2e54714540c011b13d6c96fd014dfb7d1588a04c0fabf3bdc228/kornia_rs-0.1.14-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:371ba151de638150554af9fad53a351d5c41ed80f50a73ae376b58622e0a3430", size = 3690285, upload-time = "2026-05-19T07:45:28.063Z" }, + { url = "https://files.pythonhosted.org/packages/c1/7f/95dbcdc340009efba12c159c8b8cce7edc2e2172ac628f9bbecc9ad3ca73/kornia_rs-0.1.14-cp311-cp311-win_amd64.whl", hash = "sha256:9175b704be9d2de5f1aefc6516eefa46835f71bb93605db67936996d2be42684", size = 3368862, upload-time = "2026-05-19T07:46:15.096Z" }, + { url = "https://files.pythonhosted.org/packages/d9/e3/e57480f38e395262616afd8efe91d0a1f7a4b875b52c9890e31aa85a057c/kornia_rs-0.1.14-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:76faf5389b1ea53452fc08561622ccad8ce81c8ff1857c4742be6ae4e82bf078", size = 3555313, upload-time = "2026-05-19T07:46:01.714Z" }, + { url = "https://files.pythonhosted.org/packages/e4/4f/2935c5186cce45bfa85b587883d627627427bceb61d0b0e0aa0267339910/kornia_rs-0.1.14-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0025db9854f3a34c66123c2646d52e71a534678d9343f3c897192136b2c3ddaf", size = 3349262, upload-time = "2026-05-19T07:45:47.689Z" }, + { url = "https://files.pythonhosted.org/packages/d1/f5/dc5e8f69130de7ed8d59500789bc0cf1658ada8d5360164e416622cb47f1/kornia_rs-0.1.14-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:747b26a3ce0cad76aa1047ed65f95dcd649286a2d5417d8ad93f03bb1909238d", size = 3381293, upload-time = "2026-05-19T07:45:10.552Z" }, + { url = "https://files.pythonhosted.org/packages/72/7f/01c9456a09a3a5731bf986724f6f6ff70d627ac8072cf298d842ec204692/kornia_rs-0.1.14-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:396f84661fcf260885c3f9db717caf6904eafd44857dca17be09a835bd7da8d9", size = 3695565, upload-time = "2026-05-19T07:45:30.01Z" }, + { url = "https://files.pythonhosted.org/packages/e0/6f/01e0e2cf90c47ecf26656263cdabe491a1b206c94c0c30a1dd7e4d13ed29/kornia_rs-0.1.14-cp312-cp312-win_amd64.whl", hash = "sha256:ac4bbd0a8fd73b5058a39707c790fecec4c5204a42d1f5af17f1fa57cc83d406", size = 3367565, upload-time = "2026-05-19T07:46:16.682Z" }, + { url = "https://files.pythonhosted.org/packages/ca/16/c0a4e602b0ae106dcae9ee94d115f348c7b385f175831b54acf56d886a47/kornia_rs-0.1.14-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:a703ec79a33b76115386dfef02fd36bed17715a1209fed858dd0c1adf7482421", size = 3554935, upload-time = "2026-05-19T07:46:03.239Z" }, + { url = "https://files.pythonhosted.org/packages/dc/fa/63b541cd864bdaab443cc402751f22d881e5219e409627b7cf3cbf6e737b/kornia_rs-0.1.14-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:ea26534e04f937f2f4d445e12dcbf0c291c4afbb91b3d659b03c1841b0a445d7", size = 3349072, upload-time = "2026-05-19T07:45:49.185Z" }, + { url = "https://files.pythonhosted.org/packages/32/f2/ebb0584fe11a93b4f92fbd2638c0476999003e3501557c26ceef51a5b9da/kornia_rs-0.1.14-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9f0312afdaf27fb4579d07fdf6b457b2c75e1323a4d3b1d5812a86fef0a2316e", size = 3381025, upload-time = "2026-05-19T07:45:12.785Z" }, + { url = "https://files.pythonhosted.org/packages/f2/09/3f78df732325132a3f8fceb0059c1e4736bb48e4fca8acea7d3de93ad15f/kornia_rs-0.1.14-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:65ba9214fc10cca816b7f6653f59a2bb74f343dce163adceba10926480d7a2b6", size = 3694754, upload-time = "2026-05-19T07:45:31.881Z" }, + { url = "https://files.pythonhosted.org/packages/32/76/c6f5d9dbbad23c36fa5ecbbf64bce253680080de928df85f21a9ec8dfd3e/kornia_rs-0.1.14-cp313-cp313-win_amd64.whl", hash = "sha256:4d3312002012fd0189e762b62b24d882e97e4ea9fe3a3834f01d7e17e911201c", size = 3366940, upload-time = "2026-05-19T07:46:18.703Z" }, + { url = "https://files.pythonhosted.org/packages/88/41/c88fc43775e77d2e8c134f996c8962fdc51987307b604f50a3d2298f4836/kornia_rs-0.1.14-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:45866a0691ecb491a6af3c779b25fd76dc65792710070d0673181a7f9dc38a08", size = 3543237, upload-time = "2026-05-19T07:46:04.733Z" }, + { url = "https://files.pythonhosted.org/packages/78/7f/ad46cdd7b24006914f8f50d907e0e64cdf2231f4dffce253f90785eda132/kornia_rs-0.1.14-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:2b329376d01a03e5a76a381efaaafa6fe1e54a5932eace1de95760564643ca4d", size = 3339920, upload-time = "2026-05-19T07:45:50.824Z" }, + { url = "https://files.pythonhosted.org/packages/f1/fb/62ab6d4b582eaf12ac991c1bdb94f2211bd7bbeb59c04108af9e9456904e/kornia_rs-0.1.14-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:603f56ffa0ffe2de50e5c3c4c606e5a37c98c0277a2ad752feac0e25920880f4", size = 3377444, upload-time = "2026-05-19T07:45:14.587Z" }, + { url = "https://files.pythonhosted.org/packages/d7/a8/9f6dec9c5b78ff76dca0fd63c6f3c96b8d00e2a475fcfadd7eeee0c65729/kornia_rs-0.1.14-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:496301c800afea6867220d0f02344f44a90b50c1da22d5511c25df0c0c2b4d75", size = 3686869, upload-time = "2026-05-19T07:45:33.734Z" }, + { url = "https://files.pythonhosted.org/packages/15/e7/ff8d97f922679c0f8f3aec6b27bab98c03c02feb2200543aed2e98b4b09d/kornia_rs-0.1.14-cp313-cp313t-win_amd64.whl", hash = "sha256:29cfb7b179ba0b98772bd459f6e74da67f93b290491a5c03deb9197955dfa684", size = 3359930, upload-time = "2026-05-19T07:46:20.207Z" }, + { url = "https://files.pythonhosted.org/packages/d5/49/093a3d42d52c6d5b1850f0564b82f9801575b818515a4dfbc9f82d2a7459/kornia_rs-0.1.14-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:816dd1d1713b13f3b39831d20097cb2aa69c2863c9a98555b1b32df0e5b9e309", size = 3555445, upload-time = "2026-05-19T07:46:06.481Z" }, + { url = "https://files.pythonhosted.org/packages/04/df/344d1c7e36c2cca38227b29693554c8dd1112e5b03155c5fae5fd9122df6/kornia_rs-0.1.14-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:27b23edda1f847ee4532ae2f008b16da535b947e2cb261be1865f7faff6c9fe7", size = 3349363, upload-time = "2026-05-19T07:45:52.348Z" }, + { url = "https://files.pythonhosted.org/packages/58/89/a90c7ebe73715ec5d326cf5f215f430f32fb5f208a0f7db7bc04f523d7e6/kornia_rs-0.1.14-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f1f5798b209c5e0cd6ec2629aac5b70c2b7c6c628a432a1b6a7414aca5805f9d", size = 3381572, upload-time = "2026-05-19T07:45:17.192Z" }, + { url = "https://files.pythonhosted.org/packages/ed/05/ffd6ae5b5cdddcbf9f7b7940d408c38911b8ab3911148b5b114522410ff1/kornia_rs-0.1.14-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b93a70df2ce65269de1f1e9c1fbe14e1fb2cdda6c3a39a31621b68a09cdba01d", size = 3695025, upload-time = "2026-05-19T07:45:35.916Z" }, + { url = "https://files.pythonhosted.org/packages/b0/96/56c15bd73c5ea7f242e6e03001b46db31c99fb7b287455dc36d1273f1b39/kornia_rs-0.1.14-cp314-cp314-win_amd64.whl", hash = "sha256:ff5ab2ede8eee7c05c6b55318ca96118785c40e9320e30c3fbb7f2b68b6fbe2b", size = 3366738, upload-time = "2026-05-19T07:46:21.693Z" }, + { url = "https://files.pythonhosted.org/packages/28/51/db8b3b72289a72c71a29a8d0ae683bd2f307c0b6f5d534744bdad0fd0183/kornia_rs-0.1.14-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:7726f27690cf471e8df967d71ee6c937adce764a0de0fea02aeac216b71770fc", size = 3543838, upload-time = "2026-05-19T07:46:07.958Z" }, + { url = "https://files.pythonhosted.org/packages/78/45/adc8d6642c4cda15525b50d40fae79bf80db91a12237be824cbd67059726/kornia_rs-0.1.14-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7835328d5bf1565c42ce405db125811653a207c6e5dc16e937cf4527a04d8710", size = 3341040, upload-time = "2026-05-19T07:45:53.867Z" }, + { url = "https://files.pythonhosted.org/packages/b1/62/f3855e36213a8815ae933238b68f67c7435d7f76d36a1f08da3b8a6517a3/kornia_rs-0.1.14-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4cb1a72ea7ce13a2971af16f28409c080560aa332431b3552c633197316e0869", size = 3377938, upload-time = "2026-05-19T07:45:19.273Z" }, + { url = "https://files.pythonhosted.org/packages/63/56/408f1fa7308c5ea8b3d7f5338d121580ef692f7e32858ee35f6969c158b6/kornia_rs-0.1.14-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2dc1942acd2e6cbf28f1e056518db751264550f9aaa61760ce01ede266e42b61", size = 3687771, upload-time = "2026-05-19T07:45:37.952Z" }, + { url = "https://files.pythonhosted.org/packages/22/0b/bb6d1904f92a4f1d05edb4ac39513fd7a3babc48c889bd137f09d4fd5668/kornia_rs-0.1.14-cp314-cp314t-win_amd64.whl", hash = "sha256:26b13fbf0a22c133a1957defca8460faceeb22c7ce1ab37a6f4a658944682c58", size = 3360061, upload-time = "2026-05-19T07:46:23.27Z" }, ] [[package]] @@ -881,185 +1030,261 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "packaging" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/49/ac/21a1f8aa3777f5658576777ea76bfb124b702c520bbe90edf4ae9915eafa/lazy_loader-0.5.tar.gz", hash = "sha256:717f9179a0dbed357012ddad50a5ad3d5e4d9a0b8712680d4e687f5e6e6ed9b3", size = 15294 } +sdist = { url = "https://files.pythonhosted.org/packages/49/ac/21a1f8aa3777f5658576777ea76bfb124b702c520bbe90edf4ae9915eafa/lazy_loader-0.5.tar.gz", hash = "sha256:717f9179a0dbed357012ddad50a5ad3d5e4d9a0b8712680d4e687f5e6e6ed9b3", size = 15294, upload-time = "2026-03-06T15:45:09.054Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/8a/a1/8d812e53a5da1687abb10445275d41a8b13adb781bbf7196ddbcf8d88505/lazy_loader-0.5-py3-none-any.whl", hash = "sha256:ab0ea149e9c554d4ffeeb21105ac60bed7f3b4fd69b1d2360a4add51b170b005", size = 8044 }, + { url = "https://files.pythonhosted.org/packages/8a/a1/8d812e53a5da1687abb10445275d41a8b13adb781bbf7196ddbcf8d88505/lazy_loader-0.5-py3-none-any.whl", hash = "sha256:ab0ea149e9c554d4ffeeb21105ac60bed7f3b4fd69b1d2360a4add51b170b005", size = 8044, upload-time = "2026-03-06T15:45:07.668Z" }, ] [[package]] name = "libclang" version = "18.1.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/6e/5c/ca35e19a4f142adffa27e3d652196b7362fa612243e2b916845d801454fc/libclang-18.1.1.tar.gz", hash = "sha256:a1214966d08d73d971287fc3ead8dfaf82eb07fb197680d8b3859dbbbbf78250", size = 39612 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/4b/49/f5e3e7e1419872b69f6f5e82ba56e33955a74bd537d8a1f5f1eff2f3668a/libclang-18.1.1-1-py2.py3-none-macosx_11_0_arm64.whl", hash = "sha256:0b2e143f0fac830156feb56f9231ff8338c20aecfe72b4ffe96f19e5a1dbb69a", size = 25836045 }, - { url = "https://files.pythonhosted.org/packages/e2/e5/fc61bbded91a8830ccce94c5294ecd6e88e496cc85f6704bf350c0634b70/libclang-18.1.1-py2.py3-none-macosx_10_9_x86_64.whl", hash = "sha256:6f14c3f194704e5d09769108f03185fce7acaf1d1ae4bbb2f30a72c2400cb7c5", size = 26502641 }, - { url = "https://files.pythonhosted.org/packages/db/ed/1df62b44db2583375f6a8a5e2ca5432bbdc3edb477942b9b7c848c720055/libclang-18.1.1-py2.py3-none-macosx_11_0_arm64.whl", hash = "sha256:83ce5045d101b669ac38e6da8e58765f12da2d3aafb3b9b98d88b286a60964d8", size = 26420207 }, - { url = "https://files.pythonhosted.org/packages/1d/fc/716c1e62e512ef1c160e7984a73a5fc7df45166f2ff3f254e71c58076f7c/libclang-18.1.1-py2.py3-none-manylinux2010_x86_64.whl", hash = "sha256:c533091d8a3bbf7460a00cb6c1a71da93bffe148f172c7d03b1c31fbf8aa2a0b", size = 24515943 }, - { url = "https://files.pythonhosted.org/packages/3c/3d/f0ac1150280d8d20d059608cf2d5ff61b7c3b7f7bcf9c0f425ab92df769a/libclang-18.1.1-py2.py3-none-manylinux2014_aarch64.whl", hash = "sha256:54dda940a4a0491a9d1532bf071ea3ef26e6dbaf03b5000ed94dd7174e8f9592", size = 23784972 }, - { url = "https://files.pythonhosted.org/packages/fe/2f/d920822c2b1ce9326a4c78c0c2b4aa3fde610c7ee9f631b600acb5376c26/libclang-18.1.1-py2.py3-none-manylinux2014_armv7l.whl", hash = "sha256:cf4a99b05376513717ab5d82a0db832c56ccea4fd61a69dbb7bccf2dfb207dbe", size = 20259606 }, - { url = "https://files.pythonhosted.org/packages/2d/c2/de1db8c6d413597076a4259cea409b83459b2db997c003578affdd32bf66/libclang-18.1.1-py2.py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:69f8eb8f65c279e765ffd28aaa7e9e364c776c17618af8bff22a8df58677ff4f", size = 24921494 }, - { url = "https://files.pythonhosted.org/packages/0b/2d/3f480b1e1d31eb3d6de5e3ef641954e5c67430d5ac93b7fa7e07589576c7/libclang-18.1.1-py2.py3-none-win_amd64.whl", hash = "sha256:4dd2d3b82fab35e2bf9ca717d7b63ac990a3519c7e312f19fa8e86dcc712f7fb", size = 26415083 }, - { url = "https://files.pythonhosted.org/packages/71/cf/e01dc4cc79779cd82d77888a88ae2fa424d93b445ad4f6c02bfc18335b70/libclang-18.1.1-py2.py3-none-win_arm64.whl", hash = "sha256:3f0e1f49f04d3cd198985fea0511576b0aee16f9ff0e0f0cad7f9c57ec3c20e8", size = 22361112 }, +sdist = { url = "https://files.pythonhosted.org/packages/6e/5c/ca35e19a4f142adffa27e3d652196b7362fa612243e2b916845d801454fc/libclang-18.1.1.tar.gz", hash = "sha256:a1214966d08d73d971287fc3ead8dfaf82eb07fb197680d8b3859dbbbbf78250", size = 39612, upload-time = "2024-03-17T16:04:37.434Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4b/49/f5e3e7e1419872b69f6f5e82ba56e33955a74bd537d8a1f5f1eff2f3668a/libclang-18.1.1-1-py2.py3-none-macosx_11_0_arm64.whl", hash = "sha256:0b2e143f0fac830156feb56f9231ff8338c20aecfe72b4ffe96f19e5a1dbb69a", size = 25836045, upload-time = "2024-06-30T17:40:31.646Z" }, + { url = "https://files.pythonhosted.org/packages/e2/e5/fc61bbded91a8830ccce94c5294ecd6e88e496cc85f6704bf350c0634b70/libclang-18.1.1-py2.py3-none-macosx_10_9_x86_64.whl", hash = "sha256:6f14c3f194704e5d09769108f03185fce7acaf1d1ae4bbb2f30a72c2400cb7c5", size = 26502641, upload-time = "2024-03-18T15:52:26.722Z" }, + { url = "https://files.pythonhosted.org/packages/db/ed/1df62b44db2583375f6a8a5e2ca5432bbdc3edb477942b9b7c848c720055/libclang-18.1.1-py2.py3-none-macosx_11_0_arm64.whl", hash = "sha256:83ce5045d101b669ac38e6da8e58765f12da2d3aafb3b9b98d88b286a60964d8", size = 26420207, upload-time = "2024-03-17T15:00:26.63Z" }, + { url = "https://files.pythonhosted.org/packages/1d/fc/716c1e62e512ef1c160e7984a73a5fc7df45166f2ff3f254e71c58076f7c/libclang-18.1.1-py2.py3-none-manylinux2010_x86_64.whl", hash = "sha256:c533091d8a3bbf7460a00cb6c1a71da93bffe148f172c7d03b1c31fbf8aa2a0b", size = 24515943, upload-time = "2024-03-17T16:03:45.942Z" }, + { url = "https://files.pythonhosted.org/packages/3c/3d/f0ac1150280d8d20d059608cf2d5ff61b7c3b7f7bcf9c0f425ab92df769a/libclang-18.1.1-py2.py3-none-manylinux2014_aarch64.whl", hash = "sha256:54dda940a4a0491a9d1532bf071ea3ef26e6dbaf03b5000ed94dd7174e8f9592", size = 23784972, upload-time = "2024-03-17T16:12:47.677Z" }, + { url = "https://files.pythonhosted.org/packages/fe/2f/d920822c2b1ce9326a4c78c0c2b4aa3fde610c7ee9f631b600acb5376c26/libclang-18.1.1-py2.py3-none-manylinux2014_armv7l.whl", hash = "sha256:cf4a99b05376513717ab5d82a0db832c56ccea4fd61a69dbb7bccf2dfb207dbe", size = 20259606, upload-time = "2024-03-17T16:17:42.437Z" }, + { url = "https://files.pythonhosted.org/packages/2d/c2/de1db8c6d413597076a4259cea409b83459b2db997c003578affdd32bf66/libclang-18.1.1-py2.py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:69f8eb8f65c279e765ffd28aaa7e9e364c776c17618af8bff22a8df58677ff4f", size = 24921494, upload-time = "2024-03-17T16:14:20.132Z" }, + { url = "https://files.pythonhosted.org/packages/0b/2d/3f480b1e1d31eb3d6de5e3ef641954e5c67430d5ac93b7fa7e07589576c7/libclang-18.1.1-py2.py3-none-win_amd64.whl", hash = "sha256:4dd2d3b82fab35e2bf9ca717d7b63ac990a3519c7e312f19fa8e86dcc712f7fb", size = 26415083, upload-time = "2024-03-17T16:42:21.703Z" }, + { url = "https://files.pythonhosted.org/packages/71/cf/e01dc4cc79779cd82d77888a88ae2fa424d93b445ad4f6c02bfc18335b70/libclang-18.1.1-py2.py3-none-win_arm64.whl", hash = "sha256:3f0e1f49f04d3cd198985fea0511576b0aee16f9ff0e0f0cad7f9c57ec3c20e8", size = 22361112, upload-time = "2024-03-17T16:42:59.565Z" }, +] + +[[package]] +name = "librt" +version = "0.13.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/dc/2f/3908645ddddab7120b46295e541ead308109fa48dbec7d67d7a778870d60/librt-0.13.0.tar.gz", hash = "sha256:1d2a610c14ac0d0750ee0a3ab8548e83155258387891caaca04def4bf7289781", size = 211402, upload-time = "2026-07-08T12:26:29.834Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/89/25/a6498964cfeec270c468cffdc118f69c29b412593610d55fa1327ca51ff4/librt-0.13.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1b5a7bbff495baedbd9b916c367d66854008f8f3b575908ded477c499dc60082", size = 148029, upload-time = "2026-07-08T12:24:45.961Z" }, + { url = "https://files.pythonhosted.org/packages/78/59/dc86d1bffd8e0c2818bace29d9f7783cfbb8e0673bf3673b5bbd5bbe0420/librt-0.13.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:34bc7938b9fdf14fe32a406c19c71faf894c5cee7e7474bd0be2f17200b82d14", size = 153036, upload-time = "2026-07-08T12:24:47.257Z" }, + { url = "https://files.pythonhosted.org/packages/29/3f/b923826660f02f286186cd9303d52bb05ced0a13708edc104dc8480920e3/librt-0.13.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f40e56b61b41be5f7dec938cfeffd660668cf4b5e72c78e7bd671d66b7bc2c79", size = 493062, upload-time = "2026-07-08T12:24:48.483Z" }, + { url = "https://files.pythonhosted.org/packages/88/87/6c0980a9c9b1302cb68d108906697b89eceb55889bb1dcf77c109aa56ca5/librt-0.13.0-cp311-cp311-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:9c5d02b89de5acd0379a51ec44a89476fb03df6145442e1c8ecd6bee2f91b176", size = 485510, upload-time = "2026-07-08T12:24:49.727Z" }, + { url = "https://files.pythonhosted.org/packages/32/81/795ae3b9df5dd94079fb807e38191855e023e8c6249014ae6bc3f0d9a490/librt-0.13.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7db9a3ff32ef5f7d1703d93831a3316cdf0b537de6a1cc03cc8fdd09b9194e89", size = 515909, upload-time = "2026-07-08T12:24:51.135Z" }, + { url = "https://files.pythonhosted.org/packages/20/e5/182de15abce8907108a6fdb41487de65beb5099b74dc5841b19b099168db/librt-0.13.0-cp311-cp311-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3dbb2a31882456cadc7053378e81ad7ed7693db4ac9f98ab5f81ef034aa8ec9f", size = 508620, upload-time = "2026-07-08T12:24:52.358Z" }, + { url = "https://files.pythonhosted.org/packages/32/03/33978d32db76e1f66377e8f78e42a2ca3c162143331677d1f50bbad36cfb/librt-0.13.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c6014e3c80f9c1fe268ef8b0e0ef113bac672cc032f2f93866e7ddad4f3e663d", size = 530363, upload-time = "2026-07-08T12:24:53.503Z" }, + { url = "https://files.pythonhosted.org/packages/e6/f5/b291fbd2d00f7d8287bcbf67b5aa0c6afed4bc26cef23e079629c47a2c04/librt-0.13.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:091b60a4d2174fc1ec5c34cdc0b72efb6224753d76b7da61ebeab7a191aec8bd", size = 534209, upload-time = "2026-07-08T12:24:55.138Z" }, + { url = "https://files.pythonhosted.org/packages/3e/03/6f41f17939d191bc21609f220da8509316bc62797f078545fe83be522e78/librt-0.13.0-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:66cb1138f384a191a6d75f986064841fcfdc0cea98f7bd9c9ab9b38049917588", size = 514254, upload-time = "2026-07-08T12:24:56.276Z" }, + { url = "https://files.pythonhosted.org/packages/af/c2/2e4befa5410a7443019c14abccc94ff619797171f6b72013635fb87f31d7/librt-0.13.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:17221a7569f8f292aa0014226e48aa25b8c2b08da18088cd230953d0ea0f9cd1", size = 557611, upload-time = "2026-07-08T12:24:57.561Z" }, + { url = "https://files.pythonhosted.org/packages/ab/54/8b69f81448417adbc040a2185f4e2eece1e1994b7dcfaeed4662b30f98a5/librt-0.13.0-cp311-cp311-win32.whl", hash = "sha256:fc67741da44c6eaa90e01eafb586bbba9b51eb5b6ed381ee6f5ae72eb3316d21", size = 104906, upload-time = "2026-07-08T12:24:58.806Z" }, + { url = "https://files.pythonhosted.org/packages/76/5a/f4aaf37b50f2fde12c8c663b83fdd499cdc24f957f19543d7414bfcc9e25/librt-0.13.0-cp311-cp311-win_amd64.whl", hash = "sha256:cc99dfb62b23c9207c33d0be8a2e2af7a42e21e6ea388b380a0c948c7b88953b", size = 125852, upload-time = "2026-07-08T12:25:00.065Z" }, + { url = "https://files.pythonhosted.org/packages/f2/99/bf1820e6feeabc2f218c24450ec0c995d6a91e8ba0fd3caf042c9e8adb2a/librt-0.13.0-cp311-cp311-win_arm64.whl", hash = "sha256:40ccd13c252d3fe473ffc8a57be7565abc8b64cf1b108344c859d5164f7f3e0c", size = 111832, upload-time = "2026-07-08T12:25:01.148Z" }, + { url = "https://files.pythonhosted.org/packages/f0/f4/b2933ddae222dac338476abb872641169a5cfed2c2bb5444a5b07b32b0c3/librt-0.13.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:30536798f4504c0fad0885b1d371b0539abb081e4570c9d7c641cb51141b49f0", size = 150990, upload-time = "2026-07-08T12:25:02.42Z" }, + { url = "https://files.pythonhosted.org/packages/90/ef/db98f744ca50e6efc9c95c70ee49b77aefac31f6a3fc7c83754a42d6a74f/librt-0.13.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:93d24ebb82aa4420b1409c389e7857bc35bd0b668007ac8172427d5c73cc8cc5", size = 155238, upload-time = "2026-07-08T12:25:03.681Z" }, + { url = "https://files.pythonhosted.org/packages/03/e7/a197e7bc72baf2c61ce7fdc6906a5054dc05bd8da0819aa894e4857bf87e/librt-0.13.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cb8a1adce42d8b75485a5d56a9623a50bcab995b6079f1dac59fc44034dd93d9", size = 503073, upload-time = "2026-07-08T12:25:05.049Z" }, + { url = "https://files.pythonhosted.org/packages/f8/e7/7887712e27da7c1ab80fcabb1de6eb24243964f6557cae530d4b70706dbd/librt-0.13.0-cp312-cp312-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:0763ca2ab66058174f9dee426dc64f5e0a89c24a7df8d3fe3f1836c04e25de4b", size = 496528, upload-time = "2026-07-08T12:25:06.26Z" }, + { url = "https://files.pythonhosted.org/packages/94/f0/f2283385bb6b950b26a1410f4ce51ec27231e0b3a4b925c46366d218b198/librt-0.13.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b222493da6e7b6199db9bd79502436cf5a27da3c1f7fa83c7e285444fc93fd03", size = 531786, upload-time = "2026-07-08T12:25:07.658Z" }, + { url = "https://files.pythonhosted.org/packages/36/11/69ac3b54766ffba5fd7e5acebfb048d66dbe1f9f2d14516c2b3edc59cf87/librt-0.13.0-cp312-cp312-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:fadc63331f4388c3dc90090448f682a7e9feafc11481391c1e94f2f907a3976e", size = 524393, upload-time = "2026-07-08T12:25:09.121Z" }, + { url = "https://files.pythonhosted.org/packages/61/5f/d72f95fd444a926a3c14b4e24979474116988dd57a45be242077c45d3c22/librt-0.13.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:70d9c62a4cffd9f23396cd5ef93fc5d11b31596b9b7d6306074abe3d5fcf09bd", size = 543026, upload-time = "2026-07-08T12:25:10.459Z" }, + { url = "https://files.pythonhosted.org/packages/c4/08/dcd9993ad192737a004ba263d549f8ea605b326b952e7d6205c7d4170b76/librt-0.13.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:66c0e7e6b02a155576df2c77ec933a70b72da726e248c494abf690923e624348", size = 546829, upload-time = "2026-07-08T12:25:11.716Z" }, + { url = "https://files.pythonhosted.org/packages/96/d5/6d9bb2f54e4109a956b7128836529653eb9d740f784bc47ed10a02c1000e/librt-0.13.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:ac04bcd3328eb91d99dfedf6a60d9c1f15d3434e6f6daf922f0420f7d90b85c7", size = 535700, upload-time = "2026-07-08T12:25:13.144Z" }, + { url = "https://files.pythonhosted.org/packages/8c/f2/10946922503858a359492fa27f13e86228bde702116a740ac7b3cd185f24/librt-0.13.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:db327e7271e653c32040b85ae6188059c924b57d7e1e29f935523fa017cd4e82", size = 573566, upload-time = "2026-07-08T12:25:14.336Z" }, + { url = "https://files.pythonhosted.org/packages/48/a8/94f00e3c99479a18088af3685ea016c42f3c7d5d1964d8dbb40c08d7f1aa/librt-0.13.0-cp312-cp312-win32.whl", hash = "sha256:860bd1d8ba48456ce08feaf8d343a8aaeb2fa086f2bcaa2a923fa3f7a3ff9aa3", size = 106099, upload-time = "2026-07-08T12:25:16.159Z" }, + { url = "https://files.pythonhosted.org/packages/c9/7b/2da9c74c1ed25a89cc4e1c8e007ea2eb4a0f1fafa3e70d757fe3242c5c5c/librt-0.13.0-cp312-cp312-win_amd64.whl", hash = "sha256:e54a315caf843c8d77e388cadc56ea9ded569935ee2d2347d7ea94992e5aa6fa", size = 126934, upload-time = "2026-07-08T12:25:17.275Z" }, + { url = "https://files.pythonhosted.org/packages/d0/65/aead61bbf3b5358593f9d4779d2a0e88eaf6ec191a6342dde36dd1df6371/librt-0.13.0-cp312-cp312-win_arm64.whl", hash = "sha256:c718e99a0992127af84385378460db624103b559ab260435abcfe77a4e4ed1c1", size = 112236, upload-time = "2026-07-08T12:25:18.425Z" }, + { url = "https://files.pythonhosted.org/packages/67/3b/18e7b63255297a2bdc9c25c8d6d4ca8eca9f63aceb1252c0f7427ac7099e/librt-0.13.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:a468951af16155824e88bdd8326ebe5bdb371f3ec0ac04642994b98201d914f3", size = 151027, upload-time = "2026-07-08T12:25:19.638Z" }, + { url = "https://files.pythonhosted.org/packages/4d/68/e2248452c00d1a03b45fee1752cdc8f790a476efd2402b75181da88a9e61/librt-0.13.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:ae01d8512cc17079e53425635327dbf3f7ff57a42c00dec348bf79791c56444c", size = 155152, upload-time = "2026-07-08T12:25:20.851Z" }, + { url = "https://files.pythonhosted.org/packages/0e/16/52b1c99bf19057a062aac39c900cbb81499f6f75d6c537c14463d247ba78/librt-0.13.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:32c26893cd085c1efe83219e78d866da23fb20a066101b8f68210004361d224c", size = 502499, upload-time = "2026-07-08T12:25:22.055Z" }, + { url = "https://files.pythonhosted.org/packages/9f/54/b811151805c795f55e0dedee6ec687b75f9982a8105d240ea3910737a77b/librt-0.13.0-cp313-cp313-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:5929da1981a46bcf4b28b1b9499905f0ff58e2419da402a048234e9783acbc4b", size = 496108, upload-time = "2026-07-08T12:25:23.296Z" }, + { url = "https://files.pythonhosted.org/packages/8f/f8/094d6b2bd93f3fdaa54db54cc788c4a365333bddad65ab02e04da0b1d004/librt-0.13.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:94b85d664d777bab6c0d709416cb42938251fda9e221b79e3a2215d85df5f4f9", size = 531576, upload-time = "2026-07-08T12:25:24.648Z" }, + { url = "https://files.pythonhosted.org/packages/2e/40/541733d5755824f968f7ec39d78ffbd75d145964157ae5e69a09ec6d7326/librt-0.13.0-cp313-cp313-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:531b2df3e9fe96b1fcf73a6d165921e4656be5f58d631d384ebce344298368db", size = 524390, upload-time = "2026-07-08T12:25:25.898Z" }, + { url = "https://files.pythonhosted.org/packages/c6/b5/255673cfdbf5ba663339d36cd863c897289ab4337577e19f9405ce059f36/librt-0.13.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:109b84a9edf69ad89dc1f66358659e14a031baca95e3e5b0060bd903ede8efd6", size = 543053, upload-time = "2026-07-08T12:25:27.436Z" }, + { url = "https://files.pythonhosted.org/packages/9e/11/ab5005e9c9850710f21e354201bf090646349d3fabf5f951eaf70235729e/librt-0.13.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:1304368a3e7ffc3e9db986796cc5326fdb5943a3567ecc137cff318e4240c0e7", size = 546387, upload-time = "2026-07-08T12:25:28.65Z" }, + { url = "https://files.pythonhosted.org/packages/a2/04/a5d7ce1d1df1afd15ca283dcdf7530ac073e12d69ae8c40879dda96f7868/librt-0.13.0-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:e4f9b472e7d308d94b62c801982065661158c6ed02790d6c7ddb4337cea0f9c1", size = 535970, upload-time = "2026-07-08T12:25:30.171Z" }, + { url = "https://files.pythonhosted.org/packages/5a/76/927e267a6daa290174ac281b23c9804c8829b042ade9c6f24a065f540958/librt-0.13.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9f836c37478f167a81200d8c8b2c920a22224564bed2c23d7aeec760965c367a", size = 573582, upload-time = "2026-07-08T12:25:31.507Z" }, + { url = "https://files.pythonhosted.org/packages/10/24/b6c5213efe39c19f9e13605644d0cf063b4ddaa33ac2e45b088e23a70e2e/librt-0.13.0-cp313-cp313-pyemscripten_2025_0_wasm32.whl", hash = "sha256:4000d961ff9598ac6ea603c6c836a5ed49bc205ade5fc378b998dfe1e2c36628", size = 82189, upload-time = "2026-07-08T12:25:32.675Z" }, + { url = "https://files.pythonhosted.org/packages/4c/00/d29736be177a906ac0b84a5b04b4fbfa22c776dc2f366de4172b0f968c08/librt-0.13.0-cp313-cp313-win32.whl", hash = "sha256:79e44cff71750d299d61a678e49995b0d5935a9cda238c2574daeca3ba536927", size = 106193, upload-time = "2026-07-08T12:25:33.692Z" }, + { url = "https://files.pythonhosted.org/packages/c8/ac/aff6fb45393cb8912f39dfb156ef6b2d1cadb207ff465fc8f66141054be8/librt-0.13.0-cp313-cp313-win_amd64.whl", hash = "sha256:54dab44a847d5ad1acd05c8a83fe518ae685516ecf4d3f7cc6e3df2a66767650", size = 126962, upload-time = "2026-07-08T12:25:34.769Z" }, + { url = "https://files.pythonhosted.org/packages/d9/3a/d68cb2b334d53fd30fac81d3a489ce4ba0d9506f4df43fcf676b68352b19/librt-0.13.0-cp313-cp313-win_arm64.whl", hash = "sha256:d4cb6fbfdf874340ab5e51450753c0f817b6958a3621125ee695bbc3de866566", size = 112127, upload-time = "2026-07-08T12:25:35.981Z" }, + { url = "https://files.pythonhosted.org/packages/7b/66/f49ae0d592bd45b6941e9a8bafcb6a87cddcd501ee7874707e767f01b585/librt-0.13.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:25218d94b1d2cbc0ba1d8a3f9dc9af578d9646e5ed16443a70cde1dfdcce6d71", size = 149818, upload-time = "2026-07-08T12:25:37.203Z" }, + { url = "https://files.pythonhosted.org/packages/3d/50/51c76d74014d04fb95b6506d286808984b78a2f7a41039094e6b2194ac48/librt-0.13.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:f26629539d4893c2957a16c41bb058e1e135c1f150f6a2e25ed047f64cf3f5c6", size = 154071, upload-time = "2026-07-08T12:25:39.399Z" }, + { url = "https://files.pythonhosted.org/packages/b8/fe/f19b0f5f82d5a1f2da736586bc840abd00ce07d6388136ae80b7333883fc/librt-0.13.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a4517d47b2b8af26975a406fba7d314de9696d864252e0257c6ea90238cfe27f", size = 494168, upload-time = "2026-07-08T12:25:40.641Z" }, + { url = "https://files.pythonhosted.org/packages/94/bc/b8550c75775127fd31a5f20e8775997f7b527ad661fc8ddccd7497c064f7/librt-0.13.0-cp314-cp314-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:f19e181de5b3a1148bb3420b8c4b0b0ea0fce6950099724ad151d6cea5acc180", size = 491054, upload-time = "2026-07-08T12:25:41.905Z" }, + { url = "https://files.pythonhosted.org/packages/30/14/4d0204867623df3f33f86efd3d3692ba5e01321443f4d6eab35a22697618/librt-0.13.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:22034924f5b42d5a56371cf271771bfeaabf235a7a8b6264bef2d20013f786c6", size = 523006, upload-time = "2026-07-08T12:25:43.327Z" }, + { url = "https://files.pythonhosted.org/packages/19/0a/c45fc9a260934696bace1ac5df1e148ac92bd71767aee3bf7cd7a4534f4c/librt-0.13.0-cp314-cp314-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c7897db4e95e22468bdda33d8e012ceacd0182abf001e6389d763f0def6286b9", size = 515058, upload-time = "2026-07-08T12:25:44.541Z" }, + { url = "https://files.pythonhosted.org/packages/13/0a/50c5ce45b326854ef8fa6ae4c36cf5142e5c55315eaf9e51d0ae73ac4da3/librt-0.13.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:1ce61b3746545029d4f5c17d6bd74b676254ad98433086c846ffb5e8fa73f007", size = 534025, upload-time = "2026-07-08T12:25:45.825Z" }, + { url = "https://files.pythonhosted.org/packages/89/2d/08c413c8f93fc13b8103624fce38e5caa86cd08cbbc8465870ab287af54b/librt-0.13.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:46c330e82565962c761dbce7941be2cff7db674ee807455a8d0cadc5f9b759b0", size = 540557, upload-time = "2026-07-08T12:25:47.059Z" }, + { url = "https://files.pythonhosted.org/packages/b3/c1/93af71fb4a364952210051811dd4e40174e79656b050c89cacac18af3330/librt-0.13.0-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:375f5af8f99cbaa99dd293af986e3d57caabc9ba81a5d3f021603764854197a1", size = 523201, upload-time = "2026-07-08T12:25:48.392Z" }, + { url = "https://files.pythonhosted.org/packages/c1/6e/9766f07b676a4889d9f8bc2864e9ba5fff165653143ef4dda7df6aa34d16/librt-0.13.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:9320d34c3376ae204b2cd176e8d4883a013934e0aef822f1aed9c536490c275d", size = 565740, upload-time = "2026-07-08T12:25:49.678Z" }, + { url = "https://files.pythonhosted.org/packages/a2/1e/664e3472ce2b6e10e9b83f29d4a36eb982ff6b5a169ae7567bba3a4c4ff5/librt-0.13.0-cp314-cp314-pyemscripten_2026_0_wasm32.whl", hash = "sha256:9af313c66157a69dc69ea0059a66961692250e0dc95af9c385a48ffb770a0d16", size = 81611, upload-time = "2026-07-08T12:25:50.857Z" }, + { url = "https://files.pythonhosted.org/packages/2f/d4/8582a4d65e2234673685e07309d02c230b28a85724eb0acbf13f019b7f6e/librt-0.13.0-cp314-cp314-win32.whl", hash = "sha256:f2a7253458e34f33543551394ae4fe104b497ec2a65ac266074de64c1df82e37", size = 100106, upload-time = "2026-07-08T12:25:52.03Z" }, + { url = "https://files.pythonhosted.org/packages/63/ce/0cb99efe6086b46cd985dc26672166fae312a239690e75871f7fafbd3fc5/librt-0.13.0-cp314-cp314-win_amd64.whl", hash = "sha256:a3dfe4edf10e8ed7e55b026a8bfc2c2a8704218b659cd4bffdf604fab966dc39", size = 121209, upload-time = "2026-07-08T12:25:53.166Z" }, + { url = "https://files.pythonhosted.org/packages/26/85/4f3ccb083a3c9b0d42e223acdb3c3f507953324a59cdcab4826e8e2e3b89/librt-0.13.0-cp314-cp314-win_arm64.whl", hash = "sha256:68a5faee4bba381cb93b5961f684a514cf0053cb92308ff9c792c2fea0b174c6", size = 106404, upload-time = "2026-07-08T12:25:54.253Z" }, + { url = "https://files.pythonhosted.org/packages/b2/77/333191499538c8e8189de7a4cba8e6f49ee949fd6d6e6324b21fd1522466/librt-0.13.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:a38fb81d8376dfa2f8963b265fec07637802b0d01e2a127c19c66cb070fb24f5", size = 159231, upload-time = "2026-07-08T12:25:55.432Z" }, + { url = "https://files.pythonhosted.org/packages/7a/9e/2aa83758f22c278b837a1d8025898434ce2b8bff36678d5330ecaef56dff/librt-0.13.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:d4c8d9bd5abce34b2e75edb3bf37ab0f34e49b1f915a40ae8468eb7c85bc5b46", size = 161300, upload-time = "2026-07-08T12:25:56.585Z" }, + { url = "https://files.pythonhosted.org/packages/bb/c0/86791e936553ca763d6b3c2fb4d31d596cd00e14fa631c283a40ba01559a/librt-0.13.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:387e2f1d27e89bffe0d3f520f0da0662c973fd607ca16c1808f8a5085419485e", size = 582056, upload-time = "2026-07-08T12:25:58.144Z" }, + { url = "https://files.pythonhosted.org/packages/a8/d3/a9ec15984a185e000c4d2a16ba28bd623124ad4c38a10974c7ff78e3a893/librt-0.13.0-cp314-cp314t-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:4f6db193d2e5e0ed60359b9a5a682cd67205d0d3b1e459a867dd4b5c4e7eaa7a", size = 562758, upload-time = "2026-07-08T12:25:59.544Z" }, + { url = "https://files.pythonhosted.org/packages/3c/af/dbe36b78b19c06a55097f99305e4ea9458e2273e6ae16a3cbecaad7ee978/librt-0.13.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0d38604854e8d22faadf683ec6c02bb0f886e2ba56ef981a1c36ee275f21ea22", size = 602095, upload-time = "2026-07-08T12:26:00.991Z" }, + { url = "https://files.pythonhosted.org/packages/2a/a8/2966891b4dd2830f5203fbee92ac2c4947653a2390ba73dfa44244fad025/librt-0.13.0-cp314-cp314t-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:371f7ce73026815dafd51c50ce38416e91428b28c4b2ec97cd39271164b0045c", size = 593452, upload-time = "2026-07-08T12:26:02.352Z" }, + { url = "https://files.pythonhosted.org/packages/61/f5/4df8bfc8405ecf8c0d525b4d69636f694bdd8620b313ec8b76e54a5926cc/librt-0.13.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:3aaedf52171bee90860704c560bc798fe83b76247df47568e0197e9b13c735a0", size = 623729, upload-time = "2026-07-08T12:26:04.294Z" }, + { url = "https://files.pythonhosted.org/packages/d6/13/9ac202dffc8db06f75d06c08c2f9f6ff054be67d21272dcc078fa1cc0c57/librt-0.13.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:96bad8725a4f196a798366c25ce075d1f7543a4ec045ffc13e6a7ec095cdab04", size = 617077, upload-time = "2026-07-08T12:26:05.845Z" }, + { url = "https://files.pythonhosted.org/packages/6e/f0/ebe38610716aee5cb28efd95089bb90192096179802779381e1c5dcf239c/librt-0.13.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:6bf6a559ffe4a93bbea6cf31ddf01a7fd9ba342ef51f27beb178e318b74acd61", size = 599561, upload-time = "2026-07-08T12:26:07.21Z" }, + { url = "https://files.pythonhosted.org/packages/4f/5c/c2e72e236fff7abc716d5b1753b8b8cd3ea85ac46fe17d2e7c51d4e1c723/librt-0.13.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:301067672387902c55f94b51d5022304b36c966ea9fe1f21caab99a9bef487c9", size = 645511, upload-time = "2026-07-08T12:26:08.562Z" }, + { url = "https://files.pythonhosted.org/packages/0c/99/6203ce619dee940d6bfbe099ec3fe4be00a68e9d60f70abf906cf124fe66/librt-0.13.0-cp314-cp314t-win32.whl", hash = "sha256:5fdcf34f86de8fb66d7dc7589f96ba91c4aa46671200d400e6fd6f109a483f18", size = 104357, upload-time = "2026-07-08T12:26:09.828Z" }, + { url = "https://files.pythonhosted.org/packages/52/dd/843b6314087c41657c7036d7914d8f294bdf9b580aa8513ea0588c8e9a3d/librt-0.13.0-cp314-cp314t-win_amd64.whl", hash = "sha256:260c33e92263fa629b4f6d3c51967a1c2158fe6c33237aaa3ebeac586b085259", size = 126998, upload-time = "2026-07-08T12:26:10.975Z" }, + { url = "https://files.pythonhosted.org/packages/5f/5d/3dcec2884ba1b0806d1408612555c38dd5d68e90156b59f75f6e36435c3a/librt-0.13.0-cp314-cp314t-win_arm64.whl", hash = "sha256:2f281549a4c52ac7bb97997f14353f8bd0e53a34ca0dad1c905cfd0b4a58ae99", size = 110771, upload-time = "2026-07-08T12:26:12.303Z" }, ] [[package]] name = "markdown-it-py" -version = "4.0.0" +version = "4.2.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "mdurl" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/5b/f5/4ec618ed16cc4f8fb3b701563655a69816155e79e24a17b651541804721d/markdown_it_py-4.0.0.tar.gz", hash = "sha256:cb0a2b4aa34f932c007117b194e945bd74e0ec24133ceb5bac59009cda1cb9f3", size = 73070 } +sdist = { url = "https://files.pythonhosted.org/packages/06/ff/7841249c247aa650a76b9ee4bbaeae59370dc8bfd2f6c01f3630c35eb134/markdown_it_py-4.2.0.tar.gz", hash = "sha256:04a21681d6fbb623de53f6f364d352309d4094dd4194040a10fd51833e418d49", size = 82454, upload-time = "2026-05-07T12:08:28.36Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/94/54/e7d793b573f298e1c9013b8c4dade17d481164aa517d1d7148619c2cedbf/markdown_it_py-4.0.0-py3-none-any.whl", hash = "sha256:87327c59b172c5011896038353a81343b6754500a08cd7a4973bb48c6d578147", size = 87321 }, + { url = "https://files.pythonhosted.org/packages/b3/81/4da04ced5a082363ecfa159c010d200ecbd959ae410c10c0264a38cac0f5/markdown_it_py-4.2.0-py3-none-any.whl", hash = "sha256:9f7ebbcd14fe59494226453aed97c1070d83f8d24b6fc3a3bcf9a38092641c4a", size = 91687, upload-time = "2026-05-07T12:08:27.182Z" }, ] [[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 } -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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { url = "https://files.pythonhosted.org/packages/0f/62/d9c46a7f5c9adbeeeda52f5b8d802e1094e9717705a645efc71b0913a0a8/markupsafe-3.0.3-cp311-cp311-win32.whl", hash = "sha256:0db14f5dafddbb6d9208827849fad01f1a2609380add406671a26386cdf15a19", size = 14572 }, - { url = "https://files.pythonhosted.org/packages/83/8a/4414c03d3f891739326e1783338e48fb49781cc915b2e0ee052aa490d586/markupsafe-3.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:de8a88e63464af587c950061a5e6a67d3632e36df62b986892331d4620a35c01", size = 15077 }, - { url = "https://files.pythonhosted.org/packages/35/73/893072b42e6862f319b5207adc9ae06070f095b358655f077f69a35601f0/markupsafe-3.0.3-cp311-cp311-win_arm64.whl", hash = "sha256:3b562dd9e9ea93f13d53989d23a7e775fdfd1066c33494ff43f5418bc8c58a5c", size = 13876 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { url = "https://files.pythonhosted.org/packages/2f/e1/78ee7a023dac597a5825441ebd17170785a9dab23de95d2c7508ade94e0e/markupsafe-3.0.3-cp312-cp312-win32.whl", hash = "sha256:d88b440e37a16e651bda4c7c2b930eb586fd15ca7406cb39e211fcff3bf3017d", size = 14540 }, - { url = "https://files.pythonhosted.org/packages/aa/5b/bec5aa9bbbb2c946ca2733ef9c4ca91c91b6a24580193e891b5f7dbe8e1e/markupsafe-3.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:26a5784ded40c9e318cfc2bdb30fe164bdb8665ded9cd64d500a34fb42067b1c", size = 15105 }, - { url = "https://files.pythonhosted.org/packages/e5/f1/216fc1bbfd74011693a4fd837e7026152e89c4bcf3e77b6692fba9923123/markupsafe-3.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:35add3b638a5d900e807944a078b51922212fb3dedb01633a8defc4b01a3c85f", size = 13906 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { url = "https://files.pythonhosted.org/packages/19/bc/e7140ed90c5d61d77cea142eed9f9c303f4c4806f60a1044c13e3f1471d0/markupsafe-3.0.3-cp313-cp313-win32.whl", hash = "sha256:bdd37121970bfd8be76c5fb069c7751683bdf373db1ed6c010162b2a130248ed", size = 14543 }, - { url = "https://files.pythonhosted.org/packages/05/73/c4abe620b841b6b791f2edc248f556900667a5a1cf023a6646967ae98335/markupsafe-3.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:9a1abfdc021a164803f4d485104931fb8f8c1efd55bc6b748d2f5774e78b62c5", size = 15113 }, - { url = "https://files.pythonhosted.org/packages/f0/3a/fa34a0f7cfef23cf9500d68cb7c32dd64ffd58a12b09225fb03dd37d5b80/markupsafe-3.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:7e68f88e5b8799aa49c85cd116c932a1ac15caaa3f5db09087854d218359e485", size = 13911 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { url = "https://files.pythonhosted.org/packages/80/d6/2d1b89f6ca4bff1036499b1e29a1d02d282259f3681540e16563f27ebc23/markupsafe-3.0.3-cp313-cp313t-win32.whl", hash = "sha256:69c0b73548bc525c8cb9a251cddf1931d1db4d2258e9599c28c07ef3580ef354", size = 14612 }, - { url = "https://files.pythonhosted.org/packages/2b/98/e48a4bfba0a0ffcf9925fe2d69240bfaa19c6f7507b8cd09c70684a53c1e/markupsafe-3.0.3-cp313-cp313t-win_amd64.whl", hash = "sha256:1b4b79e8ebf6b55351f0d91fe80f893b4743f104bff22e90697db1590e47a218", size = 15200 }, - { url = "https://files.pythonhosted.org/packages/0e/72/e3cc540f351f316e9ed0f092757459afbc595824ca724cbc5a5d4263713f/markupsafe-3.0.3-cp313-cp313t-win_arm64.whl", hash = "sha256:ad2cf8aa28b8c020ab2fc8287b0f823d0a7d8630784c31e9ee5edea20f406287", size = 13973 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { url = "https://files.pythonhosted.org/packages/46/11/f333a06fc16236d5238bfe74daccbca41459dcd8d1fa952e8fbd5dccfb70/markupsafe-3.0.3-cp314-cp314-win32.whl", hash = "sha256:729586769a26dbceff69f7a7dbbf59ab6572b99d94576a5592625d5b411576b9", size = 14747 }, - { url = "https://files.pythonhosted.org/packages/28/52/182836104b33b444e400b14f797212f720cbc9ed6ba34c800639d154e821/markupsafe-3.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:bdc919ead48f234740ad807933cdf545180bfbe9342c2bb451556db2ed958581", size = 15341 }, - { url = "https://files.pythonhosted.org/packages/6f/18/acf23e91bd94fd7b3031558b1f013adfa21a8e407a3fdb32745538730382/markupsafe-3.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:5a7d5dc5140555cf21a6fefbdbf8723f06fcd2f63ef108f2854de715e4422cb4", size = 14073 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { url = "https://files.pythonhosted.org/packages/fb/df/5bd7a48c256faecd1d36edc13133e51397e41b73bb77e1a69deab746ebac/markupsafe-3.0.3-cp314-cp314t-win32.whl", hash = "sha256:915c04ba3851909ce68ccc2b8e2cd691618c4dc4c4232fb7982bca3f41fd8c3d", size = 14819 }, - { url = "https://files.pythonhosted.org/packages/1a/8a/0402ba61a2f16038b48b39bccca271134be00c5c9f0f623208399333c448/markupsafe-3.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4faffd047e07c38848ce017e8725090413cd80cbc23d86e55c587bf979e579c9", size = 15426 }, - { url = "https://files.pythonhosted.org/packages/70/bc/6f1c2f612465f5fa89b95bead1f44dcb607670fd42891d8fdcd5d039f4f4/markupsafe-3.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:32001d6a8fc98c8cb5c947787c5d08b0a50663d139f1305bac5885d98d9b40fa", size = 14146 }, +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.10.8" +version = "3.11.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "contourpy" }, { name = "cycler" }, { name = "fonttools" }, { name = "kiwisolver" }, - { name = "numpy" }, + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, + { name = "numpy", version = "2.5.1", 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/8a/76/d3c6e3a13fe484ebe7718d14e269c9569c4eb0020a968a327acb3b9a8fe6/matplotlib-3.10.8.tar.gz", hash = "sha256:2299372c19d56bcd35cf05a2738308758d32b9eaed2371898d8f5bd33f084aa3", size = 34806269 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/f8/86/de7e3a1cdcfc941483af70609edc06b83e7c8a0e0dc9ac325200a3f4d220/matplotlib-3.10.8-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:6be43b667360fef5c754dda5d25a32e6307a03c204f3c0fc5468b78fa87b4160", size = 8251215 }, - { url = "https://files.pythonhosted.org/packages/fd/14/baad3222f424b19ce6ad243c71de1ad9ec6b2e4eb1e458a48fdc6d120401/matplotlib-3.10.8-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a2b336e2d91a3d7006864e0990c83b216fcdca64b5a6484912902cef87313d78", size = 8139625 }, - { url = "https://files.pythonhosted.org/packages/8f/a0/7024215e95d456de5883e6732e708d8187d9753a21d32f8ddb3befc0c445/matplotlib-3.10.8-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:efb30e3baaea72ce5928e32bab719ab4770099079d66726a62b11b1ef7273be4", size = 8712614 }, - { url = "https://files.pythonhosted.org/packages/5a/f4/b8347351da9a5b3f41e26cf547252d861f685c6867d179a7c9d60ad50189/matplotlib-3.10.8-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d56a1efd5bfd61486c8bc968fa18734464556f0fb8e51690f4ac25d85cbbbbc2", size = 9540997 }, - { url = "https://files.pythonhosted.org/packages/9e/c0/c7b914e297efe0bc36917bf216b2acb91044b91e930e878ae12981e461e5/matplotlib-3.10.8-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:238b7ce5717600615c895050239ec955d91f321c209dd110db988500558e70d6", size = 9596825 }, - { url = "https://files.pythonhosted.org/packages/6f/d3/a4bbc01c237ab710a1f22b4da72f4ff6d77eb4c7735ea9811a94ae239067/matplotlib-3.10.8-cp311-cp311-win_amd64.whl", hash = "sha256:18821ace09c763ec93aef5eeff087ee493a24051936d7b9ebcad9662f66501f9", size = 8135090 }, - { url = "https://files.pythonhosted.org/packages/89/dd/a0b6588f102beab33ca6f5218b31725216577b2a24172f327eaf6417d5c9/matplotlib-3.10.8-cp311-cp311-win_arm64.whl", hash = "sha256:bab485bcf8b1c7d2060b4fcb6fc368a9e6f4cd754c9c2fea281f4be21df394a2", size = 8012377 }, - { url = "https://files.pythonhosted.org/packages/9e/67/f997cdcbb514012eb0d10cd2b4b332667997fb5ebe26b8d41d04962fa0e6/matplotlib-3.10.8-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:64fcc24778ca0404ce0cb7b6b77ae1f4c7231cdd60e6778f999ee05cbd581b9a", size = 8260453 }, - { url = "https://files.pythonhosted.org/packages/7e/65/07d5f5c7f7c994f12c768708bd2e17a4f01a2b0f44a1c9eccad872433e2e/matplotlib-3.10.8-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b9a5ca4ac220a0cdd1ba6bcba3608547117d30468fefce49bb26f55c1a3d5c58", size = 8148321 }, - { url = "https://files.pythonhosted.org/packages/3e/f3/c5195b1ae57ef85339fd7285dfb603b22c8b4e79114bae5f4f0fcf688677/matplotlib-3.10.8-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3ab4aabc72de4ff77b3ec33a6d78a68227bf1123465887f9905ba79184a1cc04", size = 8716944 }, - { url = "https://files.pythonhosted.org/packages/00/f9/7638f5cc82ec8a7aa005de48622eecc3ed7c9854b96ba15bd76b7fd27574/matplotlib-3.10.8-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:24d50994d8c5816ddc35411e50a86ab05f575e2530c02752e02538122613371f", size = 9550099 }, - { url = "https://files.pythonhosted.org/packages/57/61/78cd5920d35b29fd2a0fe894de8adf672ff52939d2e9b43cb83cd5ce1bc7/matplotlib-3.10.8-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:99eefd13c0dc3b3c1b4d561c1169e65fe47aab7b8158754d7c084088e2329466", size = 9613040 }, - { url = "https://files.pythonhosted.org/packages/30/4e/c10f171b6e2f44d9e3a2b96efa38b1677439d79c99357600a62cc1e9594e/matplotlib-3.10.8-cp312-cp312-win_amd64.whl", hash = "sha256:dd80ecb295460a5d9d260df63c43f4afbdd832d725a531f008dad1664f458adf", size = 8142717 }, - { url = "https://files.pythonhosted.org/packages/f1/76/934db220026b5fef85f45d51a738b91dea7d70207581063cd9bd8fafcf74/matplotlib-3.10.8-cp312-cp312-win_arm64.whl", hash = "sha256:3c624e43ed56313651bc18a47f838b60d7b8032ed348911c54906b130b20071b", size = 8012751 }, - { url = "https://files.pythonhosted.org/packages/3d/b9/15fd5541ef4f5b9a17eefd379356cf12175fe577424e7b1d80676516031a/matplotlib-3.10.8-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:3f2e409836d7f5ac2f1c013110a4d50b9f7edc26328c108915f9075d7d7a91b6", size = 8261076 }, - { url = "https://files.pythonhosted.org/packages/8d/a0/2ba3473c1b66b9c74dc7107c67e9008cb1782edbe896d4c899d39ae9cf78/matplotlib-3.10.8-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:56271f3dac49a88d7fca5060f004d9d22b865f743a12a23b1e937a0be4818ee1", size = 8148794 }, - { url = "https://files.pythonhosted.org/packages/75/97/a471f1c3eb1fd6f6c24a31a5858f443891d5127e63a7788678d14e249aea/matplotlib-3.10.8-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:a0a7f52498f72f13d4a25ea70f35f4cb60642b466cbb0a9be951b5bc3f45a486", size = 8718474 }, - { url = "https://files.pythonhosted.org/packages/01/be/cd478f4b66f48256f42927d0acbcd63a26a893136456cd079c0cc24fbabf/matplotlib-3.10.8-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:646d95230efb9ca614a7a594d4fcacde0ac61d25e37dd51710b36477594963ce", size = 9549637 }, - { url = "https://files.pythonhosted.org/packages/5d/7c/8dc289776eae5109e268c4fb92baf870678dc048a25d4ac903683b86d5bf/matplotlib-3.10.8-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f89c151aab2e2e23cb3fe0acad1e8b82841fd265379c4cecd0f3fcb34c15e0f6", size = 9613678 }, - { url = "https://files.pythonhosted.org/packages/64/40/37612487cc8a437d4dd261b32ca21fe2d79510fe74af74e1f42becb1bdb8/matplotlib-3.10.8-cp313-cp313-win_amd64.whl", hash = "sha256:e8ea3e2d4066083e264e75c829078f9e149fa119d27e19acd503de65e0b13149", size = 8142686 }, - { url = "https://files.pythonhosted.org/packages/66/52/8d8a8730e968185514680c2a6625943f70269509c3dcfc0dcf7d75928cb8/matplotlib-3.10.8-cp313-cp313-win_arm64.whl", hash = "sha256:c108a1d6fa78a50646029cb6d49808ff0fc1330fda87fa6f6250c6b5369b6645", size = 8012917 }, - { url = "https://files.pythonhosted.org/packages/b5/27/51fe26e1062f298af5ef66343d8ef460e090a27fea73036c76c35821df04/matplotlib-3.10.8-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:ad3d9833a64cf48cc4300f2b406c3d0f4f4724a91c0bd5640678a6ba7c102077", size = 8305679 }, - { url = "https://files.pythonhosted.org/packages/2c/1e/4de865bc591ac8e3062e835f42dd7fe7a93168d519557837f0e37513f629/matplotlib-3.10.8-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:eb3823f11823deade26ce3b9f40dcb4a213da7a670013929f31d5f5ed1055b22", size = 8198336 }, - { url = "https://files.pythonhosted.org/packages/c6/cb/2f7b6e75fb4dce87ef91f60cac4f6e34f4c145ab036a22318ec837971300/matplotlib-3.10.8-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d9050fee89a89ed57b4fb2c1bfac9a3d0c57a0d55aed95949eedbc42070fea39", size = 8731653 }, - { url = "https://files.pythonhosted.org/packages/46/b3/bd9c57d6ba670a37ab31fb87ec3e8691b947134b201f881665b28cc039ff/matplotlib-3.10.8-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b44d07310e404ba95f8c25aa5536f154c0a8ec473303535949e52eb71d0a1565", size = 9561356 }, - { url = "https://files.pythonhosted.org/packages/c0/3d/8b94a481456dfc9dfe6e39e93b5ab376e50998cddfd23f4ae3b431708f16/matplotlib-3.10.8-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:0a33deb84c15ede243aead39f77e990469fff93ad1521163305095b77b72ce4a", size = 9614000 }, - { url = "https://files.pythonhosted.org/packages/bd/cd/bc06149fe5585ba800b189a6a654a75f1f127e8aab02fd2be10df7fa500c/matplotlib-3.10.8-cp313-cp313t-win_amd64.whl", hash = "sha256:3a48a78d2786784cc2413e57397981fb45c79e968d99656706018d6e62e57958", size = 8220043 }, - { url = "https://files.pythonhosted.org/packages/e3/de/b22cf255abec916562cc04eef457c13e58a1990048de0c0c3604d082355e/matplotlib-3.10.8-cp313-cp313t-win_arm64.whl", hash = "sha256:15d30132718972c2c074cd14638c7f4592bd98719e2308bccea40e0538bc0cb5", size = 8062075 }, - { url = "https://files.pythonhosted.org/packages/3c/43/9c0ff7a2f11615e516c3b058e1e6e8f9614ddeca53faca06da267c48345d/matplotlib-3.10.8-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:b53285e65d4fa4c86399979e956235deb900be5baa7fc1218ea67fbfaeaadd6f", size = 8262481 }, - { url = "https://files.pythonhosted.org/packages/6f/ca/e8ae28649fcdf039fda5ef554b40a95f50592a3c47e6f7270c9561c12b07/matplotlib-3.10.8-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:32f8dce744be5569bebe789e46727946041199030db8aeb2954d26013a0eb26b", size = 8151473 }, - { url = "https://files.pythonhosted.org/packages/f1/6f/009d129ae70b75e88cbe7e503a12a4c0670e08ed748a902c2568909e9eb5/matplotlib-3.10.8-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4cf267add95b1c88300d96ca837833d4112756045364f5c734a2276038dae27d", size = 9553896 }, - { url = "https://files.pythonhosted.org/packages/f5/26/4221a741eb97967bc1fd5e4c52b9aa5a91b2f4ec05b59f6def4d820f9df9/matplotlib-3.10.8-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2cf5bd12cecf46908f286d7838b2abc6c91cda506c0445b8223a7c19a00df008", size = 9824193 }, - { url = "https://files.pythonhosted.org/packages/1f/f3/3abf75f38605772cf48a9daf5821cd4f563472f38b4b828c6fba6fa6d06e/matplotlib-3.10.8-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:41703cc95688f2516b480f7f339d8851a6035f18e100ee6a32bc0b8536a12a9c", size = 9615444 }, - { url = "https://files.pythonhosted.org/packages/93/a5/de89ac80f10b8dc615807ee1133cd99ac74082581196d4d9590bea10690d/matplotlib-3.10.8-cp314-cp314-win_amd64.whl", hash = "sha256:83d282364ea9f3e52363da262ce32a09dfe241e4080dcedda3c0db059d3c1f11", size = 8272719 }, - { url = "https://files.pythonhosted.org/packages/69/ce/b006495c19ccc0a137b48083168a37bd056392dee02f87dba0472f2797fe/matplotlib-3.10.8-cp314-cp314-win_arm64.whl", hash = "sha256:2c1998e92cd5999e295a731bcb2911c75f597d937341f3030cc24ef2733d78a8", size = 8144205 }, - { url = "https://files.pythonhosted.org/packages/68/d9/b31116a3a855bd313c6fcdb7226926d59b041f26061c6c5b1be66a08c826/matplotlib-3.10.8-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:b5a2b97dbdc7d4f353ebf343744f1d1f1cca8aa8bfddb4262fcf4306c3761d50", size = 8305785 }, - { url = "https://files.pythonhosted.org/packages/1e/90/6effe8103f0272685767ba5f094f453784057072f49b393e3ea178fe70a5/matplotlib-3.10.8-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:3f5c3e4da343bba819f0234186b9004faba952cc420fbc522dc4e103c1985908", size = 8198361 }, - { url = "https://files.pythonhosted.org/packages/d7/65/a73188711bea603615fc0baecca1061429ac16940e2385433cc778a9d8e7/matplotlib-3.10.8-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5f62550b9a30afde8c1c3ae450e5eb547d579dd69b25c2fc7a1c67f934c1717a", size = 9561357 }, - { url = "https://files.pythonhosted.org/packages/f4/3d/b5c5d5d5be8ce63292567f0e2c43dde9953d3ed86ac2de0a72e93c8f07a1/matplotlib-3.10.8-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:495672de149445ec1b772ff2c9ede9b769e3cb4f0d0aa7fa730d7f59e2d4e1c1", size = 9823610 }, - { url = "https://files.pythonhosted.org/packages/4d/4b/e7beb6bbd49f6bae727a12b270a2654d13c397576d25bd6786e47033300f/matplotlib-3.10.8-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:595ba4d8fe983b88f0eec8c26a241e16d6376fe1979086232f481f8f3f67494c", size = 9614011 }, - { url = "https://files.pythonhosted.org/packages/7c/e6/76f2813d31f032e65f6f797e3f2f6e4aab95b65015924b1c51370395c28a/matplotlib-3.10.8-cp314-cp314t-win_amd64.whl", hash = "sha256:25d380fe8b1dc32cf8f0b1b448470a77afb195438bafdf1d858bfb876f3edf7b", size = 8362801 }, - { url = "https://files.pythonhosted.org/packages/5d/49/d651878698a0b67f23aa28e17f45a6d6dd3d3f933fa29087fa4ce5947b5a/matplotlib-3.10.8-cp314-cp314t-win_arm64.whl", hash = "sha256:113bb52413ea508ce954a02c10ffd0d565f9c3bc7f2eddc27dfe1731e71c7b5f", size = 8192560 }, - { url = "https://files.pythonhosted.org/packages/04/30/3afaa31c757f34b7725ab9d2ba8b48b5e89c2019c003e7d0ead143aabc5a/matplotlib-3.10.8-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:6da7c2ce169267d0d066adcf63758f0604aa6c3eebf67458930f9d9b79ad1db1", size = 8249198 }, - { url = "https://files.pythonhosted.org/packages/48/2f/6334aec331f57485a642a7c8be03cb286f29111ae71c46c38b363230063c/matplotlib-3.10.8-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:9153c3292705be9f9c64498a8872118540c3f4123d1a1c840172edf262c8be4a", size = 8136817 }, - { url = "https://files.pythonhosted.org/packages/73/e4/6d6f14b2a759c622f191b2d67e9075a3f56aaccb3be4bb9bb6890030d0a0/matplotlib-3.10.8-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1ae029229a57cd1e8fe542485f27e7ca7b23aa9e8944ddb4985d0bc444f1eca2", size = 8713867 }, +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 = "mdurl" version = "0.1.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", size = 8729 } +sdist = { url = "https://files.pythonhosted.org/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", size = 8729, upload-time = "2022-08-14T12:40:10.846Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979 }, + { url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979, upload-time = "2022-08-14T12:40:09.779Z" }, ] [[package]] @@ -1067,449 +1292,590 @@ name = "ml-dtypes" version = "0.5.4" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "numpy" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/0e/4a/c27b42ed9b1c7d13d9ba8b6905dece787d6259152f2309338aed29b2447b/ml_dtypes-0.5.4.tar.gz", hash = "sha256:8ab06a50fb9bf9666dd0fe5dfb4676fa2b0ac0f31ecff72a6c3af8e22c063453", size = 692314 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/c6/5e/712092cfe7e5eb667b8ad9ca7c54442f21ed7ca8979745f1000e24cf8737/ml_dtypes-0.5.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6c7ecb74c4bd71db68a6bea1edf8da8c34f3d9fe218f038814fd1d310ac76c90", size = 679734 }, - { url = "https://files.pythonhosted.org/packages/4f/cf/912146dfd4b5c0eea956836c01dcd2fce6c9c844b2691f5152aca196ce4f/ml_dtypes-0.5.4-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bc11d7e8c44a65115d05e2ab9989d1e045125d7be8e05a071a48bc76eb6d6040", size = 5056165 }, - { url = "https://files.pythonhosted.org/packages/a9/80/19189ea605017473660e43762dc853d2797984b3c7bf30ce656099add30c/ml_dtypes-0.5.4-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:19b9a53598f21e453ea2fbda8aa783c20faff8e1eeb0d7ab899309a0053f1483", size = 5034975 }, - { url = "https://files.pythonhosted.org/packages/b4/24/70bd59276883fdd91600ca20040b41efd4902a923283c4d6edcb1de128d2/ml_dtypes-0.5.4-cp311-cp311-win_amd64.whl", hash = "sha256:7c23c54a00ae43edf48d44066a7ec31e05fdc2eee0be2b8b50dd1903a1db94bb", size = 210742 }, - { url = "https://files.pythonhosted.org/packages/a0/c9/64230ef14e40aa3f1cb254ef623bf812735e6bec7772848d19131111ac0d/ml_dtypes-0.5.4-cp311-cp311-win_arm64.whl", hash = "sha256:557a31a390b7e9439056644cb80ed0735a6e3e3bb09d67fd5687e4b04238d1de", size = 160709 }, - { url = "https://files.pythonhosted.org/packages/a8/b8/3c70881695e056f8a32f8b941126cf78775d9a4d7feba8abcb52cb7b04f2/ml_dtypes-0.5.4-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:a174837a64f5b16cab6f368171a1a03a27936b31699d167684073ff1c4237dac", size = 676927 }, - { url = "https://files.pythonhosted.org/packages/54/0f/428ef6881782e5ebb7eca459689448c0394fa0a80bea3aa9262cba5445ea/ml_dtypes-0.5.4-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a7f7c643e8b1320fd958bf098aa7ecf70623a42ec5154e3be3be673f4c34d900", size = 5028464 }, - { url = "https://files.pythonhosted.org/packages/3a/cb/28ce52eb94390dda42599c98ea0204d74799e4d8047a0eb559b6fd648056/ml_dtypes-0.5.4-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9ad459e99793fa6e13bd5b7e6792c8f9190b4e5a1b45c63aba14a4d0a7f1d5ff", size = 5009002 }, - { url = "https://files.pythonhosted.org/packages/f5/f0/0cfadd537c5470378b1b32bd859cf2824972174b51b873c9d95cfd7475a5/ml_dtypes-0.5.4-cp312-cp312-win_amd64.whl", hash = "sha256:c1a953995cccb9e25a4ae19e34316671e4e2edaebe4cf538229b1fc7109087b7", size = 212222 }, - { url = "https://files.pythonhosted.org/packages/16/2e/9acc86985bfad8f2c2d30291b27cd2bb4c74cea08695bd540906ed744249/ml_dtypes-0.5.4-cp312-cp312-win_arm64.whl", hash = "sha256:9bad06436568442575beb2d03389aa7456c690a5b05892c471215bfd8cf39460", size = 160793 }, - { url = "https://files.pythonhosted.org/packages/d9/a1/4008f14bbc616cfb1ac5b39ea485f9c63031c4634ab3f4cf72e7541f816a/ml_dtypes-0.5.4-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:8c760d85a2f82e2bed75867079188c9d18dae2ee77c25a54d60e9cc79be1bc48", size = 676888 }, - { url = "https://files.pythonhosted.org/packages/d3/b7/dff378afc2b0d5a7d6cd9d3209b60474d9819d1189d347521e1688a60a53/ml_dtypes-0.5.4-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ce756d3a10d0c4067172804c9cc276ba9cc0ff47af9078ad439b075d1abdc29b", size = 5036993 }, - { url = "https://files.pythonhosted.org/packages/eb/33/40cd74219417e78b97c47802037cf2d87b91973e18bb968a7da48a96ea44/ml_dtypes-0.5.4-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:533ce891ba774eabf607172254f2e7260ba5f57bdd64030c9a4fcfbd99815d0d", size = 5010956 }, - { url = "https://files.pythonhosted.org/packages/e1/8b/200088c6859d8221454825959df35b5244fa9bdf263fd0249ac5fb75e281/ml_dtypes-0.5.4-cp313-cp313-win_amd64.whl", hash = "sha256:f21c9219ef48ca5ee78402d5cc831bd58ea27ce89beda894428bc67a52da5328", size = 212224 }, - { url = "https://files.pythonhosted.org/packages/8f/75/dfc3775cb36367816e678f69a7843f6f03bd4e2bcd79941e01ea960a068e/ml_dtypes-0.5.4-cp313-cp313-win_arm64.whl", hash = "sha256:35f29491a3e478407f7047b8a4834e4640a77d2737e0b294d049746507af5175", size = 160798 }, - { url = "https://files.pythonhosted.org/packages/4f/74/e9ddb35fd1dd43b1106c20ced3f53c2e8e7fc7598c15638e9f80677f81d4/ml_dtypes-0.5.4-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:304ad47faa395415b9ccbcc06a0350800bc50eda70f0e45326796e27c62f18b6", size = 702083 }, - { url = "https://files.pythonhosted.org/packages/74/f5/667060b0aed1aa63166b22897fdf16dca9eb704e6b4bbf86848d5a181aa7/ml_dtypes-0.5.4-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6a0df4223b514d799b8a1629c65ddc351b3efa833ccf7f8ea0cf654a61d1e35d", size = 5354111 }, - { url = "https://files.pythonhosted.org/packages/40/49/0f8c498a28c0efa5f5c95a9e374c83ec1385ca41d0e85e7cf40e5d519a21/ml_dtypes-0.5.4-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:531eff30e4d368cb6255bc2328d070e35836aa4f282a0fb5f3a0cd7260257298", size = 5366453 }, - { url = "https://files.pythonhosted.org/packages/8c/27/12607423d0a9c6bbbcc780ad19f1f6baa2b68b18ce4bddcdc122c4c68dc9/ml_dtypes-0.5.4-cp313-cp313t-win_amd64.whl", hash = "sha256:cb73dccfc991691c444acc8c0012bee8f2470da826a92e3a20bb333b1a7894e6", size = 225612 }, - { url = "https://files.pythonhosted.org/packages/e5/80/5a5929e92c72936d5b19872c5fb8fc09327c1da67b3b68c6a13139e77e20/ml_dtypes-0.5.4-cp313-cp313t-win_arm64.whl", hash = "sha256:3bbbe120b915090d9dd1375e4684dd17a20a2491ef25d640a908281da85e73f1", size = 164145 }, - { url = "https://files.pythonhosted.org/packages/72/4e/1339dc6e2557a344f5ba5590872e80346f76f6cb2ac3dd16e4666e88818c/ml_dtypes-0.5.4-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:2b857d3af6ac0d39db1de7c706e69c7f9791627209c3d6dedbfca8c7e5faec22", size = 673781 }, - { url = "https://files.pythonhosted.org/packages/04/f9/067b84365c7e83bda15bba2b06c6ca250ce27b20630b1128c435fb7a09aa/ml_dtypes-0.5.4-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:805cef3a38f4eafae3a5bf9ebdcdb741d0bcfd9e1bd90eb54abd24f928cd2465", size = 5036145 }, - { url = "https://files.pythonhosted.org/packages/c6/bb/82c7dcf38070b46172a517e2334e665c5bf374a262f99a283ea454bece7c/ml_dtypes-0.5.4-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:14a4fd3228af936461db66faccef6e4f41c1d82fcc30e9f8d58a08916b1d811f", size = 5010230 }, - { url = "https://files.pythonhosted.org/packages/e9/93/2bfed22d2498c468f6bcd0d9f56b033eaa19f33320389314c19ef6766413/ml_dtypes-0.5.4-cp314-cp314-win_amd64.whl", hash = "sha256:8c6a2dcebd6f3903e05d51960a8058d6e131fe69f952a5397e5dbabc841b6d56", size = 221032 }, - { url = "https://files.pythonhosted.org/packages/76/a3/9c912fe6ea747bb10fe2f8f54d027eb265db05dfb0c6335e3e063e74e6e8/ml_dtypes-0.5.4-cp314-cp314-win_arm64.whl", hash = "sha256:5a0f68ca8fd8d16583dfa7793973feb86f2fbb56ce3966daf9c9f748f52a2049", size = 163353 }, - { url = "https://files.pythonhosted.org/packages/cd/02/48aa7d84cc30ab4ee37624a2fd98c56c02326785750cd212bc0826c2f15b/ml_dtypes-0.5.4-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:bfc534409c5d4b0bf945af29e5d0ab075eae9eecbb549ff8a29280db822f34f9", size = 702085 }, - { url = "https://files.pythonhosted.org/packages/5a/e7/85cb99fe80a7a5513253ec7faa88a65306be071163485e9a626fce1b6e84/ml_dtypes-0.5.4-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2314892cdc3fcf05e373d76d72aaa15fda9fb98625effa73c1d646f331fcecb7", size = 5355358 }, - { url = "https://files.pythonhosted.org/packages/79/2b/a826ba18d2179a56e144aef69e57fb2ab7c464ef0b2111940ee8a3a223a2/ml_dtypes-0.5.4-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0d2ffd05a2575b1519dc928c0b93c06339eb67173ff53acb00724502cda231cf", size = 5366332 }, - { url = "https://files.pythonhosted.org/packages/84/44/f4d18446eacb20ea11e82f133ea8f86e2bf2891785b67d9da8d0ab0ef525/ml_dtypes-0.5.4-cp314-cp314t-win_amd64.whl", hash = "sha256:4381fe2f2452a2d7589689693d3162e876b3ddb0a832cde7a414f8e1adf7eab1", size = 236612 }, - { url = "https://files.pythonhosted.org/packages/ad/3f/3d42e9a78fe5edf792a83c074b13b9b770092a4fbf3462872f4303135f09/ml_dtypes-0.5.4-cp314-cp314t-win_arm64.whl", hash = "sha256:11942cbf2cf92157db91e5022633c0d9474d4dfd813a909383bd23ce828a4b7d", size = 168825 }, + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, + { name = "numpy", version = "2.5.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/0e/4a/c27b42ed9b1c7d13d9ba8b6905dece787d6259152f2309338aed29b2447b/ml_dtypes-0.5.4.tar.gz", hash = "sha256:8ab06a50fb9bf9666dd0fe5dfb4676fa2b0ac0f31ecff72a6c3af8e22c063453", size = 692314, upload-time = "2025-11-17T22:32:31.031Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c6/5e/712092cfe7e5eb667b8ad9ca7c54442f21ed7ca8979745f1000e24cf8737/ml_dtypes-0.5.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6c7ecb74c4bd71db68a6bea1edf8da8c34f3d9fe218f038814fd1d310ac76c90", size = 679734, upload-time = "2025-11-17T22:31:39.223Z" }, + { url = "https://files.pythonhosted.org/packages/4f/cf/912146dfd4b5c0eea956836c01dcd2fce6c9c844b2691f5152aca196ce4f/ml_dtypes-0.5.4-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bc11d7e8c44a65115d05e2ab9989d1e045125d7be8e05a071a48bc76eb6d6040", size = 5056165, upload-time = "2025-11-17T22:31:41.071Z" }, + { url = "https://files.pythonhosted.org/packages/a9/80/19189ea605017473660e43762dc853d2797984b3c7bf30ce656099add30c/ml_dtypes-0.5.4-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:19b9a53598f21e453ea2fbda8aa783c20faff8e1eeb0d7ab899309a0053f1483", size = 5034975, upload-time = "2025-11-17T22:31:42.758Z" }, + { url = "https://files.pythonhosted.org/packages/b4/24/70bd59276883fdd91600ca20040b41efd4902a923283c4d6edcb1de128d2/ml_dtypes-0.5.4-cp311-cp311-win_amd64.whl", hash = "sha256:7c23c54a00ae43edf48d44066a7ec31e05fdc2eee0be2b8b50dd1903a1db94bb", size = 210742, upload-time = "2025-11-17T22:31:44.068Z" }, + { url = "https://files.pythonhosted.org/packages/a0/c9/64230ef14e40aa3f1cb254ef623bf812735e6bec7772848d19131111ac0d/ml_dtypes-0.5.4-cp311-cp311-win_arm64.whl", hash = "sha256:557a31a390b7e9439056644cb80ed0735a6e3e3bb09d67fd5687e4b04238d1de", size = 160709, upload-time = "2025-11-17T22:31:46.557Z" }, + { url = "https://files.pythonhosted.org/packages/a8/b8/3c70881695e056f8a32f8b941126cf78775d9a4d7feba8abcb52cb7b04f2/ml_dtypes-0.5.4-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:a174837a64f5b16cab6f368171a1a03a27936b31699d167684073ff1c4237dac", size = 676927, upload-time = "2025-11-17T22:31:48.182Z" }, + { url = "https://files.pythonhosted.org/packages/54/0f/428ef6881782e5ebb7eca459689448c0394fa0a80bea3aa9262cba5445ea/ml_dtypes-0.5.4-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a7f7c643e8b1320fd958bf098aa7ecf70623a42ec5154e3be3be673f4c34d900", size = 5028464, upload-time = "2025-11-17T22:31:50.135Z" }, + { url = "https://files.pythonhosted.org/packages/3a/cb/28ce52eb94390dda42599c98ea0204d74799e4d8047a0eb559b6fd648056/ml_dtypes-0.5.4-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9ad459e99793fa6e13bd5b7e6792c8f9190b4e5a1b45c63aba14a4d0a7f1d5ff", size = 5009002, upload-time = "2025-11-17T22:31:52.001Z" }, + { url = "https://files.pythonhosted.org/packages/f5/f0/0cfadd537c5470378b1b32bd859cf2824972174b51b873c9d95cfd7475a5/ml_dtypes-0.5.4-cp312-cp312-win_amd64.whl", hash = "sha256:c1a953995cccb9e25a4ae19e34316671e4e2edaebe4cf538229b1fc7109087b7", size = 212222, upload-time = "2025-11-17T22:31:53.742Z" }, + { url = "https://files.pythonhosted.org/packages/16/2e/9acc86985bfad8f2c2d30291b27cd2bb4c74cea08695bd540906ed744249/ml_dtypes-0.5.4-cp312-cp312-win_arm64.whl", hash = "sha256:9bad06436568442575beb2d03389aa7456c690a5b05892c471215bfd8cf39460", size = 160793, upload-time = "2025-11-17T22:31:55.358Z" }, + { url = "https://files.pythonhosted.org/packages/d9/a1/4008f14bbc616cfb1ac5b39ea485f9c63031c4634ab3f4cf72e7541f816a/ml_dtypes-0.5.4-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:8c760d85a2f82e2bed75867079188c9d18dae2ee77c25a54d60e9cc79be1bc48", size = 676888, upload-time = "2025-11-17T22:31:56.907Z" }, + { url = "https://files.pythonhosted.org/packages/d3/b7/dff378afc2b0d5a7d6cd9d3209b60474d9819d1189d347521e1688a60a53/ml_dtypes-0.5.4-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ce756d3a10d0c4067172804c9cc276ba9cc0ff47af9078ad439b075d1abdc29b", size = 5036993, upload-time = "2025-11-17T22:31:58.497Z" }, + { url = "https://files.pythonhosted.org/packages/eb/33/40cd74219417e78b97c47802037cf2d87b91973e18bb968a7da48a96ea44/ml_dtypes-0.5.4-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:533ce891ba774eabf607172254f2e7260ba5f57bdd64030c9a4fcfbd99815d0d", size = 5010956, upload-time = "2025-11-17T22:31:59.931Z" }, + { url = "https://files.pythonhosted.org/packages/e1/8b/200088c6859d8221454825959df35b5244fa9bdf263fd0249ac5fb75e281/ml_dtypes-0.5.4-cp313-cp313-win_amd64.whl", hash = "sha256:f21c9219ef48ca5ee78402d5cc831bd58ea27ce89beda894428bc67a52da5328", size = 212224, upload-time = "2025-11-17T22:32:01.349Z" }, + { url = "https://files.pythonhosted.org/packages/8f/75/dfc3775cb36367816e678f69a7843f6f03bd4e2bcd79941e01ea960a068e/ml_dtypes-0.5.4-cp313-cp313-win_arm64.whl", hash = "sha256:35f29491a3e478407f7047b8a4834e4640a77d2737e0b294d049746507af5175", size = 160798, upload-time = "2025-11-17T22:32:02.864Z" }, + { url = "https://files.pythonhosted.org/packages/4f/74/e9ddb35fd1dd43b1106c20ced3f53c2e8e7fc7598c15638e9f80677f81d4/ml_dtypes-0.5.4-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:304ad47faa395415b9ccbcc06a0350800bc50eda70f0e45326796e27c62f18b6", size = 702083, upload-time = "2025-11-17T22:32:04.08Z" }, + { url = "https://files.pythonhosted.org/packages/74/f5/667060b0aed1aa63166b22897fdf16dca9eb704e6b4bbf86848d5a181aa7/ml_dtypes-0.5.4-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6a0df4223b514d799b8a1629c65ddc351b3efa833ccf7f8ea0cf654a61d1e35d", size = 5354111, upload-time = "2025-11-17T22:32:05.546Z" }, + { url = "https://files.pythonhosted.org/packages/40/49/0f8c498a28c0efa5f5c95a9e374c83ec1385ca41d0e85e7cf40e5d519a21/ml_dtypes-0.5.4-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:531eff30e4d368cb6255bc2328d070e35836aa4f282a0fb5f3a0cd7260257298", size = 5366453, upload-time = "2025-11-17T22:32:07.115Z" }, + { url = "https://files.pythonhosted.org/packages/8c/27/12607423d0a9c6bbbcc780ad19f1f6baa2b68b18ce4bddcdc122c4c68dc9/ml_dtypes-0.5.4-cp313-cp313t-win_amd64.whl", hash = "sha256:cb73dccfc991691c444acc8c0012bee8f2470da826a92e3a20bb333b1a7894e6", size = 225612, upload-time = "2025-11-17T22:32:08.615Z" }, + { url = "https://files.pythonhosted.org/packages/e5/80/5a5929e92c72936d5b19872c5fb8fc09327c1da67b3b68c6a13139e77e20/ml_dtypes-0.5.4-cp313-cp313t-win_arm64.whl", hash = "sha256:3bbbe120b915090d9dd1375e4684dd17a20a2491ef25d640a908281da85e73f1", size = 164145, upload-time = "2025-11-17T22:32:09.782Z" }, + { url = "https://files.pythonhosted.org/packages/72/4e/1339dc6e2557a344f5ba5590872e80346f76f6cb2ac3dd16e4666e88818c/ml_dtypes-0.5.4-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:2b857d3af6ac0d39db1de7c706e69c7f9791627209c3d6dedbfca8c7e5faec22", size = 673781, upload-time = "2025-11-17T22:32:11.364Z" }, + { url = "https://files.pythonhosted.org/packages/04/f9/067b84365c7e83bda15bba2b06c6ca250ce27b20630b1128c435fb7a09aa/ml_dtypes-0.5.4-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:805cef3a38f4eafae3a5bf9ebdcdb741d0bcfd9e1bd90eb54abd24f928cd2465", size = 5036145, upload-time = "2025-11-17T22:32:12.783Z" }, + { url = "https://files.pythonhosted.org/packages/c6/bb/82c7dcf38070b46172a517e2334e665c5bf374a262f99a283ea454bece7c/ml_dtypes-0.5.4-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:14a4fd3228af936461db66faccef6e4f41c1d82fcc30e9f8d58a08916b1d811f", size = 5010230, upload-time = "2025-11-17T22:32:14.38Z" }, + { url = "https://files.pythonhosted.org/packages/e9/93/2bfed22d2498c468f6bcd0d9f56b033eaa19f33320389314c19ef6766413/ml_dtypes-0.5.4-cp314-cp314-win_amd64.whl", hash = "sha256:8c6a2dcebd6f3903e05d51960a8058d6e131fe69f952a5397e5dbabc841b6d56", size = 221032, upload-time = "2025-11-17T22:32:15.763Z" }, + { url = "https://files.pythonhosted.org/packages/76/a3/9c912fe6ea747bb10fe2f8f54d027eb265db05dfb0c6335e3e063e74e6e8/ml_dtypes-0.5.4-cp314-cp314-win_arm64.whl", hash = "sha256:5a0f68ca8fd8d16583dfa7793973feb86f2fbb56ce3966daf9c9f748f52a2049", size = 163353, upload-time = "2025-11-17T22:32:16.932Z" }, + { url = "https://files.pythonhosted.org/packages/cd/02/48aa7d84cc30ab4ee37624a2fd98c56c02326785750cd212bc0826c2f15b/ml_dtypes-0.5.4-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:bfc534409c5d4b0bf945af29e5d0ab075eae9eecbb549ff8a29280db822f34f9", size = 702085, upload-time = "2025-11-17T22:32:18.175Z" }, + { url = "https://files.pythonhosted.org/packages/5a/e7/85cb99fe80a7a5513253ec7faa88a65306be071163485e9a626fce1b6e84/ml_dtypes-0.5.4-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2314892cdc3fcf05e373d76d72aaa15fda9fb98625effa73c1d646f331fcecb7", size = 5355358, upload-time = "2025-11-17T22:32:19.7Z" }, + { url = "https://files.pythonhosted.org/packages/79/2b/a826ba18d2179a56e144aef69e57fb2ab7c464ef0b2111940ee8a3a223a2/ml_dtypes-0.5.4-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0d2ffd05a2575b1519dc928c0b93c06339eb67173ff53acb00724502cda231cf", size = 5366332, upload-time = "2025-11-17T22:32:21.193Z" }, + { url = "https://files.pythonhosted.org/packages/84/44/f4d18446eacb20ea11e82f133ea8f86e2bf2891785b67d9da8d0ab0ef525/ml_dtypes-0.5.4-cp314-cp314t-win_amd64.whl", hash = "sha256:4381fe2f2452a2d7589689693d3162e876b3ddb0a832cde7a414f8e1adf7eab1", size = 236612, upload-time = "2025-11-17T22:32:22.579Z" }, + { url = "https://files.pythonhosted.org/packages/ad/3f/3d42e9a78fe5edf792a83c074b13b9b770092a4fbf3462872f4303135f09/ml_dtypes-0.5.4-cp314-cp314t-win_arm64.whl", hash = "sha256:11942cbf2cf92157db91e5022633c0d9474d4dfd813a909383bd23ce828a4b7d", size = 168825, upload-time = "2025-11-17T22:32:23.766Z" }, ] [[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 } +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 }, + { 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 = "mypy" +version = "2.3.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "ast-serialize" }, + { name = "librt", marker = "platform_python_implementation != 'PyPy'" }, + { name = "mypy-extensions" }, + { name = "pathspec" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/12/af/4e516a05d3ca2eb9283e9ec45b2c02225c1514dd6da49fd3c9eaa6639370/mypy-2.3.0.tar.gz", hash = "sha256:465965d41cd9a2726694e983e8ce7113259327bec798115d1e1dfa2a52fb666e", size = 3988104, upload-time = "2026-07-13T11:34:53.387Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e6/b9/d75b3082b05f1b3028828aeb18e74ae5ab0a0936051bbf1f32f59f654747/mypy-2.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3419d00717afbc5265b50dd14b1278f29ea4884dd398ab67873489ac093fd329", size = 14838725, upload-time = "2026-07-13T11:32:44.655Z" }, + { url = "https://files.pythonhosted.org/packages/a9/50/79a65c6ea6e115bc73296038a4543b2d5c91f07912b918a2c616a2514bba/mypy-2.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:cfca8ee88544090f86b6dcce05ec55d66eb48a762412ac2507810ba4bd793b6f", size = 13911128, upload-time = "2026-07-13T11:32:02.021Z" }, + { url = "https://files.pythonhosted.org/packages/90/48/e11ed7716c26953ca321f726e452e374dbf81a6f2b8b212ec02af29b6b8f/mypy-2.3.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:75cbb4b9ef04a0c84a957f07abc4504fbf64b8dcc145675101f2d3a78a4b1d6a", size = 14146742, upload-time = "2026-07-13T11:33:03.313Z" }, + { url = "https://files.pythonhosted.org/packages/06/72/6807565b1c4861ef66f7fdd98b51c61556356eab80235717b46c53bb8627/mypy-2.3.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:982e3d53dd23d0a4cef67dd66791fdbede0cf38f9eb617bf47663554c51e1e36", size = 15081418, upload-time = "2026-07-13T11:31:13.899Z" }, + { url = "https://files.pythonhosted.org/packages/00/80/1ea14c5d80e589e415973db3e47c78c2219a305b808b2b506395342c1d79/mypy-2.3.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:85c5385b93012ffa3b31479ab579aef5415f4f3a32c6cf1ae07a984d2a0ff461", size = 15328164, upload-time = "2026-07-13T11:31:35.723Z" }, + { url = "https://files.pythonhosted.org/packages/37/28/8223157404a3d51920078459c37f80fbdc590e1d8ea049dc5ce48643022a/mypy-2.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:13b1b16e2fa39f3b2e33fb1c468abc7a69369fa2e886b4b87b5afc81472325cd", size = 11136472, upload-time = "2026-07-13T11:27:37.018Z" }, + { url = "https://files.pythonhosted.org/packages/6f/cc/ea27e5959c5f258585a756b252031f3b313583d81b5064b2bebc41d3706b/mypy-2.3.0-cp311-cp311-win_arm64.whl", hash = "sha256:b5cd2f027a972a4a5f2278a11fac9747f5f81a53a30b714d74950b6807e55568", size = 10135800, upload-time = "2026-07-13T11:30:08.92Z" }, + { url = "https://files.pythonhosted.org/packages/dc/94/0e7e592619e2133596a47cdd642534b0456545c218430bd3b9d8fefdd1b1/mypy-2.3.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:2d53fc67b9d28a43c6199077f49fea0f05839e36cf6158500331c9549225e5a5", size = 15026523, upload-time = "2026-07-13T11:34:49.206Z" }, + { url = "https://files.pythonhosted.org/packages/f6/d2/1e1731df090a857df2807177a4626863e5ac0f0256513c35780efe53986f/mypy-2.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fbc00cee7bdbb9291979ddc9d08034a29dfcda4932628c9bbc28c1edd589df0c", size = 14032189, upload-time = "2026-07-13T11:33:57.168Z" }, + { url = "https://files.pythonhosted.org/packages/44/95/cab921f4a806e171f34113e6181dd23c55358ccf6a80741269ef594a410e/mypy-2.3.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:04e617030eca5221909c8b7d8d7fd1c637948199aa2100b2ad9813feb07e1491", size = 14198696, upload-time = "2026-07-13T11:32:12.767Z" }, + { url = "https://files.pythonhosted.org/packages/66/80/e6d008bb19fe446e3662d85e0e2717bf9f2d611a2164fb29d6e067dbf46c/mypy-2.3.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:56c184d2c20ca6b6378d58d1960270a767f41f5e44acbbd27f05effef4f4e1d7", size = 15286904, upload-time = "2026-07-13T11:34:27.594Z" }, + { url = "https://files.pythonhosted.org/packages/db/83/94397c9293608a364aa03e8084fb34ede4ae976a260384b9b52929308135/mypy-2.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3961a4a34b05f7c74b0f05aa51fbfe99a2d1e126038df40318d15c8f558b7ef3", size = 15528342, upload-time = "2026-07-13T11:34:07.819Z" }, + { url = "https://files.pythonhosted.org/packages/cf/96/d8b37d819adec6cfccfb1fd3afc1735d94717ddeafb45536db9c6943e09b/mypy-2.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:b1942b9314d4c784b8ea1dbab4972603290e5dd5630f06675f13aec97526bc4c", size = 11218346, upload-time = "2026-07-13T11:28:27.745Z" }, + { url = "https://files.pythonhosted.org/packages/2b/cd/cd9f725b19b19e5b530a154cf9bcf9e94279c5d55b3c34fb42b3aa48ea1b/mypy-2.3.0-cp312-cp312-win_arm64.whl", hash = "sha256:be51653d7669d7d7955d613b8d0bb57d5b652eaf71a873ddf65ac87254dd2595", size = 10204525, upload-time = "2026-07-13T11:31:02.552Z" }, + { url = "https://files.pythonhosted.org/packages/6e/ae/f7d056eb0294586a572d0d0d89580ec633c064db520f11d37d5a2fb833bd/mypy-2.3.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:91ad22a52ae2c7e621c2f67c94d5a17f66b3209a4cff5cf8a573579835c69e97", size = 14947298, upload-time = "2026-07-13T11:27:47.734Z" }, + { url = "https://files.pythonhosted.org/packages/32/d5/db3e7af01e7844d21662c6ddc1f7825ec7cb4053f0391ac02faf3638396f/mypy-2.3.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:99ac767cc5d3b64c8d0ae226ead10c96694f94e4e7da1668642225dcd4e75aac", size = 13950768, upload-time = "2026-07-13T11:27:57.726Z" }, + { url = "https://files.pythonhosted.org/packages/d9/fb/43c031f0190513d1ec248ed037eceb742ddd2a4d74bbf406658a28173837/mypy-2.3.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:de6d2c484742a4d7b0ed6d07b143375624d3b899c5749c7b3c947f56261f48a6", size = 14151586, upload-time = "2026-07-13T11:29:18.615Z" }, + { url = "https://files.pythonhosted.org/packages/ec/c3/f8b2ffc60883084da91be51af58e88a7ffd4ff9795acb7d902ff88d31eb1/mypy-2.3.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7da939dd335cfd2ad788bdfd081c9f4e47634ab995e5a45eb15fd1e5bc052f8b", size = 15227411, upload-time = "2026-07-13T11:30:29.904Z" }, + { url = "https://files.pythonhosted.org/packages/83/2e/16b917fc7adcf03f1aadddfc93aab804ffb234b1ab09c0ffd6d92a5d34a2/mypy-2.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:7247eb2824f996722a949530183394921ca71deb9680052a338cf53cff7925c2", size = 15478790, upload-time = "2026-07-13T11:33:14.686Z" }, + { url = "https://files.pythonhosted.org/packages/c0/88/aaa65a93c73d0cdae7e42f8adb302bf6885bb281302084f99d0290a35347/mypy-2.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:75b0984bb3cbd76bb5c9291a8671f7ae66ca3b51c7584c358fc2e923259f0757", size = 11234919, upload-time = "2026-07-13T11:33:39.28Z" }, + { url = "https://files.pythonhosted.org/packages/35/19/b40de63f1a80e63bc2d40f0679a6a8dbd34e95176c8122119bdf406aa552/mypy-2.3.0-cp313-cp313-win_arm64.whl", hash = "sha256:d78fcf900b59cb7e82cb7e3a235e31b462d9333d92285bd1e4952d355b8ffba1", size = 10201510, upload-time = "2026-07-13T11:31:52.619Z" }, + { url = "https://files.pythonhosted.org/packages/a4/58/fa0ae047da911f540284009b4f44b96fe09d83c076d7c103e9d645f46303/mypy-2.3.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:ea317b060ce83e26050f8f9e4d7d6bf44ed7597c8ff9990bccffbb9d1d8522db", size = 14941909, upload-time = "2026-07-13T11:32:34.332Z" }, + { url = "https://files.pythonhosted.org/packages/15/14/2ba1d61452d7c2a7fe12741e8d374e52b183476b07aa7f9e2a0d02b0720a/mypy-2.3.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:094af99f92638aa92852326188b85a89e50f4a472f44827c03362228482f0762", size = 13967581, upload-time = "2026-07-13T11:30:00.587Z" }, + { url = "https://files.pythonhosted.org/packages/ed/5a/483fb9e5ffbbb1a28dccc7b0a13d141b17ac769b6c9f488c0a0c63698962/mypy-2.3.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:de121747278144fc9ae7caa2e978cf5df12aebc82933182f5b3b86081a30baef", size = 14168807, upload-time = "2026-07-13T11:28:48.6Z" }, + { url = "https://files.pythonhosted.org/packages/ae/77/70d7a10732063beb74ad713682cf871e88f5c5fa39bfc8beff8a524bf9cb/mypy-2.3.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:37fa4de896a84e2dc9200d91e614c22563b43d1a266789d4bbac7b22ebe6192b", size = 15200144, upload-time = "2026-07-13T11:31:25.283Z" }, + { url = "https://files.pythonhosted.org/packages/56/72/766218ac783be4fdfcd699b90037b63017348a3e86fb2c1fbfb18302637d/mypy-2.3.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:f1b3a98dfd21058bc759bb3337d5d1f61d0fdf9f3cf9c00f4291790fb5427bff", size = 15460389, upload-time = "2026-07-13T11:29:29.077Z" }, + { url = "https://files.pythonhosted.org/packages/38/4e/8a9db7411ecb8ec0cb1fd05dba432f28bafffcd38b4e887714a4a0506689/mypy-2.3.0-cp314-cp314-pyemscripten_2026_0_wasm32.whl", hash = "sha256:944c665d984157cb96a679dfb7a4a81dd1d36b24b9c284b699514e6e626b82d4", size = 7753664, upload-time = "2026-07-13T11:29:08.147Z" }, + { url = "https://files.pythonhosted.org/packages/65/4c/c3f8bfd6ed0e5e38b5a244403b27f821d433443df5a15a278417c10a3a3c/mypy-2.3.0-cp314-cp314-win_amd64.whl", hash = "sha256:4359424140d985192c778c1ce2c114a10c1ca58a381ed79cfa70d37df94b299f", size = 11417237, upload-time = "2026-07-13T11:33:47.467Z" }, + { url = "https://files.pythonhosted.org/packages/3c/00/89a32eaf5ccf174bc4f90db0eaea5d70636c01b8d49f384bdab2e8834390/mypy-2.3.0-cp314-cp314-win_arm64.whl", hash = "sha256:3dd0bed92c4bdec57c42505b96416fb9e6a5aa7be84d2809bcd5f2ecec2860d7", size = 10389252, upload-time = "2026-07-13T11:31:43.81Z" }, + { url = "https://files.pythonhosted.org/packages/31/56/104f93d69aa9f339b6b9d3b0a7faa699b8b466c942cf3ae86cc2a2ec0915/mypy-2.3.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:691fdc37132b1ae628d834f672e74de83462d9fb4aff621835767fb43a8dd373", size = 16385495, upload-time = "2026-07-13T11:29:49.818Z" }, + { url = "https://files.pythonhosted.org/packages/d2/03/f1d2123313f55efafdd27706960f43a771c62f1b68426c76043f3ab9ebf3/mypy-2.3.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:aec15d465d477558fd842757b487849007311cf3897849cdda0e3162ac0ac556", size = 15098155, upload-time = "2026-07-13T11:30:40.301Z" }, + { url = "https://files.pythonhosted.org/packages/e5/5d/d5f9200399b445e81726c4f23becee33f233aee81c72680b1ef3a258b641/mypy-2.3.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b352b7e49f5e6576009e8df730e1ff4f915cb565b851b396d2ffe2f5a6f5da88", size = 15514155, upload-time = "2026-07-13T11:34:38.569Z" }, + { url = "https://files.pythonhosted.org/packages/cd/ce/69977c555f08faa3190cfde44189b89dbd56861b1ab97aa18fc5f3a2e4a3/mypy-2.3.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1c6c6bf687b17f90dbfcad95b960d32eaa0154c00da45f03ab50bf8952e047fe", size = 16766351, upload-time = "2026-07-13T11:33:29.195Z" }, + { url = "https://files.pythonhosted.org/packages/bc/92/6648b6caa3ab9e00f9ac0c2a78307805f873dd48139b24a6f6f7c3667bbf/mypy-2.3.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:f4ed18f111bfe2d599bca7468e7f9251042c1c2118f762c8de2766a56d773c60", size = 17043490, upload-time = "2026-07-13T11:30:53.927Z" }, + { url = "https://files.pythonhosted.org/packages/7c/ab/0dc91d80f3f016634c68d451f294a97320fe903a9b6f90b9e57b3f7f1717/mypy-2.3.0-cp314-cp314t-win_amd64.whl", hash = "sha256:0b025a93cffb9781d231f232be07a17912f35f10a313c24f301c81e842870654", size = 12146869, upload-time = "2026-07-13T11:29:38.874Z" }, + { url = "https://files.pythonhosted.org/packages/85/b5/4c964d02634ba81f4d1c84838e5c5b18ab06d13ed568960f5d6318495ccc/mypy-2.3.0-cp314-cp314t-win_arm64.whl", hash = "sha256:adebc76aab4f3495a88b41d48aa4aff0c03f2822501da76625afcca5975f19e5", size = 10965113, upload-time = "2026-07-13T11:28:07.056Z" }, + { url = "https://files.pythonhosted.org/packages/2c/fa/fdc54fe583ba3cafbcedfb70eeeaf03849f75b1827a07096c7bd996f582d/mypy-2.3.0-py3-none-any.whl", hash = "sha256:6b1cdb579446b60432432b2b2403a6201b4b475a004d7f488511c9ba177c9e88", size = 2753292, upload-time = "2026-07-13T11:33:18.48Z" }, +] + +[[package]] +name = "mypy-extensions" +version = "1.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a2/6e/371856a3fb9d31ca8dac321cda606860fa4548858c0cc45d9d1d4ca2628b/mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558", size = 6343, upload-time = "2025-04-22T14:54:24.164Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505", size = 4963, upload-time = "2025-04-22T14:54:22.983Z" }, ] [[package]] name = "namex" version = "0.1.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/0c/c0/ee95b28f029c73f8d49d8f52edaed02a1d4a9acb8b69355737fdb1faa191/namex-0.1.0.tar.gz", hash = "sha256:117f03ccd302cc48e3f5c58a296838f6b89c83455ab8683a1e85f2a430aa4306", size = 6649 } +sdist = { url = "https://files.pythonhosted.org/packages/0c/c0/ee95b28f029c73f8d49d8f52edaed02a1d4a9acb8b69355737fdb1faa191/namex-0.1.0.tar.gz", hash = "sha256:117f03ccd302cc48e3f5c58a296838f6b89c83455ab8683a1e85f2a430aa4306", size = 6649, upload-time = "2025-05-26T23:17:38.918Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b2/bc/465daf1de06409cdd4532082806770ee0d8d7df434da79c76564d0f69741/namex-0.1.0-py3-none-any.whl", hash = "sha256:e2012a474502f1e2251267062aae3114611f07df4224b6e06334c57b0f2ce87c", size = 5905 }, + { url = "https://files.pythonhosted.org/packages/b2/bc/465daf1de06409cdd4532082806770ee0d8d7df434da79c76564d0f69741/namex-0.1.0-py3-none-any.whl", hash = "sha256:e2012a474502f1e2251267062aae3114611f07df4224b6e06334c57b0f2ce87c", size = 5905, upload-time = "2025-05-26T23:17:37.695Z" }, ] [[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 } +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 }, + { 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 = "nodeenv" version = "1.10.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/24/bf/d1bda4f6168e0b2e9e5958945e01910052158313224ada5ce1fb2e1113b8/nodeenv-1.10.0.tar.gz", hash = "sha256:996c191ad80897d076bdfba80a41994c2b47c68e224c542b48feba42ba00f8bb", size = 55611 } +sdist = { url = "https://files.pythonhosted.org/packages/24/bf/d1bda4f6168e0b2e9e5958945e01910052158313224ada5ce1fb2e1113b8/nodeenv-1.10.0.tar.gz", hash = "sha256:996c191ad80897d076bdfba80a41994c2b47c68e224c542b48feba42ba00f8bb", size = 55611, upload-time = "2025-12-20T14:08:54.006Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/88/b2/d0896bdcdc8d28a7fc5717c305f1a861c26e18c05047949fb371034d98bd/nodeenv-1.10.0-py2.py3-none-any.whl", hash = "sha256:5bb13e3eed2923615535339b3c620e76779af4cb4c6a90deccc9e36b274d3827", size = 23438 }, + { url = "https://files.pythonhosted.org/packages/88/b2/d0896bdcdc8d28a7fc5717c305f1a861c26e18c05047949fb371034d98bd/nodeenv-1.10.0-py2.py3-none-any.whl", hash = "sha256:5bb13e3eed2923615535339b3c620e76779af4cb4c6a90deccc9e36b274d3827", size = 23438, upload-time = "2025-12-20T14:08:52.782Z" }, +] + +[[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 == 'emscripten'", + "python_full_version < '3.12' and sys_platform != 'darwin' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version < '3.12' and sys_platform == 'darwin'", +] +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.4.4" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d7/9f/b8cef5bffa569759033adda9481211426f12f53299629b410340795c2514/numpy-2.4.4.tar.gz", hash = "sha256:2d390634c5182175533585cc89f3608a4682ccb173cc9bb940b2881c8d6f8fa0", size = 20731587 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ef/c6/4218570d8c8ecc9704b5157a3348e486e84ef4be0ed3e38218ab473c83d2/numpy-2.4.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f983334aea213c99992053ede6168500e5f086ce74fbc4acc3f2b00f5762e9db", size = 16976799 }, - { url = "https://files.pythonhosted.org/packages/dd/92/b4d922c4a5f5dab9ed44e6153908a5c665b71acf183a83b93b690996e39b/numpy-2.4.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:72944b19f2324114e9dc86a159787333b77874143efcf89a5167ef83cfee8af0", size = 14971552 }, - { url = "https://files.pythonhosted.org/packages/8a/dc/df98c095978fa6ee7b9a9387d1d58cbb3d232d0e69ad169a4ce784bde4fd/numpy-2.4.4-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:86b6f55f5a352b48d7fbfd2dbc3d5b780b2d79f4d3c121f33eb6efb22e9a2015", size = 5476566 }, - { url = "https://files.pythonhosted.org/packages/28/34/b3fdcec6e725409223dd27356bdf5a3c2cc2282e428218ecc9cb7acc9763/numpy-2.4.4-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:ba1f4fc670ed79f876f70082eff4f9583c15fb9a4b89d6188412de4d18ae2f40", size = 6806482 }, - { url = "https://files.pythonhosted.org/packages/68/62/63417c13aa35d57bee1337c67446761dc25ea6543130cf868eace6e8157b/numpy-2.4.4-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8a87ec22c87be071b6bdbd27920b129b94f2fc964358ce38f3822635a3e2e03d", size = 15973376 }, - { url = "https://files.pythonhosted.org/packages/cf/c5/9fcb7e0e69cef59cf10c746b84f7d58b08bc66a6b7d459783c5a4f6101a6/numpy-2.4.4-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:df3775294accfdd75f32c74ae39fcba920c9a378a2fc18a12b6820aa8c1fb502", size = 16925137 }, - { url = "https://files.pythonhosted.org/packages/7e/43/80020edacb3f84b9efdd1591120a4296462c23fd8db0dde1666f6ef66f13/numpy-2.4.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0d4e437e295f18ec29bc79daf55e8a47a9113df44d66f702f02a293d93a2d6dd", size = 17329414 }, - { url = "https://files.pythonhosted.org/packages/fd/06/af0658593b18a5f73532d377188b964f239eb0894e664a6c12f484472f97/numpy-2.4.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:6aa3236c78803afbcb255045fbef97a9e25a1f6c9888357d205ddc42f4d6eba5", size = 18658397 }, - { url = "https://files.pythonhosted.org/packages/e6/ce/13a09ed65f5d0ce5c7dd0669250374c6e379910f97af2c08c57b0608eee4/numpy-2.4.4-cp311-cp311-win32.whl", hash = "sha256:30caa73029a225b2d40d9fae193e008e24b2026b7ee1a867b7ee8d96ca1a448e", size = 6239499 }, - { url = "https://files.pythonhosted.org/packages/bd/63/05d193dbb4b5eec1eca73822d80da98b511f8328ad4ae3ca4caf0f4db91d/numpy-2.4.4-cp311-cp311-win_amd64.whl", hash = "sha256:6bbe4eb67390b0a0265a2c25458f6b90a409d5d069f1041e6aff1e27e3d9a79e", size = 12614257 }, - { url = "https://files.pythonhosted.org/packages/87/c5/8168052f080c26fa984c413305012be54741c9d0d74abd7fbeeccae3889f/numpy-2.4.4-cp311-cp311-win_arm64.whl", hash = "sha256:fcfe2045fd2e8f3cb0ce9d4ba6dba6333b8fa05bb8a4939c908cd43322d14c7e", size = 10486775 }, - { url = "https://files.pythonhosted.org/packages/28/05/32396bec30fb2263770ee910142f49c1476d08e8ad41abf8403806b520ce/numpy-2.4.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:15716cfef24d3a9762e3acdf87e27f58dc823d1348f765bbea6bef8c639bfa1b", size = 16689272 }, - { url = "https://files.pythonhosted.org/packages/c5/f3/a983d28637bfcd763a9c7aafdb6d5c0ebf3d487d1e1459ffdb57e2f01117/numpy-2.4.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:23cbfd4c17357c81021f21540da84ee282b9c8fba38a03b7b9d09ba6b951421e", size = 14699573 }, - { url = "https://files.pythonhosted.org/packages/9b/fd/e5ecca1e78c05106d98028114f5c00d3eddb41207686b2b7de3e477b0e22/numpy-2.4.4-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:8b3b60bb7cba2c8c81837661c488637eee696f59a877788a396d33150c35d842", size = 5204782 }, - { url = "https://files.pythonhosted.org/packages/de/2f/702a4594413c1a8632092beae8aba00f1d67947389369b3777aed783fdca/numpy-2.4.4-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:e4a010c27ff6f210ff4c6ef34394cd61470d01014439b192ec22552ee867f2a8", size = 6552038 }, - { url = "https://files.pythonhosted.org/packages/7f/37/eed308a8f56cba4d1fdf467a4fc67ef4ff4bf1c888f5fc980481890104b1/numpy-2.4.4-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f9e75681b59ddaa5e659898085ae0eaea229d054f2ac0c7e563a62205a700121", size = 15670666 }, - { url = "https://files.pythonhosted.org/packages/0a/0d/0e3ecece05b7a7e87ab9fb587855548da437a061326fff64a223b6dcb78a/numpy-2.4.4-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:81f4a14bee47aec54f883e0cad2d73986640c1590eb9bfaaba7ad17394481e6e", size = 16645480 }, - { url = "https://files.pythonhosted.org/packages/34/49/f2312c154b82a286758ee2f1743336d50651f8b5195db18cdb63675ff649/numpy-2.4.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:62d6b0f03b694173f9fcb1fb317f7222fd0b0b103e784c6549f5e53a27718c44", size = 17020036 }, - { url = "https://files.pythonhosted.org/packages/7b/e9/736d17bd77f1b0ec4f9901aaec129c00d59f5d84d5e79bba540ef12c2330/numpy-2.4.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fbc356aae7adf9e6336d336b9c8111d390a05df88f1805573ebb0807bd06fd1d", size = 18368643 }, - { url = "https://files.pythonhosted.org/packages/63/f6/d417977c5f519b17c8a5c3bc9e8304b0908b0e21136fe43bf628a1343914/numpy-2.4.4-cp312-cp312-win32.whl", hash = "sha256:0d35aea54ad1d420c812bfa0385c71cd7cc5bcf7c65fed95fc2cd02fe8c79827", size = 5961117 }, - { url = "https://files.pythonhosted.org/packages/2d/5b/e1deebf88ff431b01b7406ca3583ab2bbb90972bbe1c568732e49c844f7e/numpy-2.4.4-cp312-cp312-win_amd64.whl", hash = "sha256:b5f0362dc928a6ecd9db58868fca5e48485205e3855957bdedea308f8672ea4a", size = 12320584 }, - { url = "https://files.pythonhosted.org/packages/58/89/e4e856ac82a68c3ed64486a544977d0e7bdd18b8da75b78a577ca31c4395/numpy-2.4.4-cp312-cp312-win_arm64.whl", hash = "sha256:846300f379b5b12cc769334464656bc882e0735d27d9726568bc932fdc49d5ec", size = 10221450 }, - { url = "https://files.pythonhosted.org/packages/14/1d/d0a583ce4fefcc3308806a749a536c201ed6b5ad6e1322e227ee4848979d/numpy-2.4.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:08f2e31ed5e6f04b118e49821397f12767934cfdd12a1ce86a058f91e004ee50", size = 16684933 }, - { url = "https://files.pythonhosted.org/packages/c1/62/2b7a48fbb745d344742c0277f01286dead15f3f68e4f359fbfcf7b48f70f/numpy-2.4.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e823b8b6edc81e747526f70f71a9c0a07ac4e7ad13020aa736bb7c9d67196115", size = 14694532 }, - { url = "https://files.pythonhosted.org/packages/e5/87/499737bfba066b4a3bebff24a8f1c5b2dee410b209bc6668c9be692580f0/numpy-2.4.4-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:4a19d9dba1a76618dd86b164d608566f393f8ec6ac7c44f0cc879011c45e65af", size = 5199661 }, - { url = "https://files.pythonhosted.org/packages/cd/da/464d551604320d1491bc345efed99b4b7034143a85787aab78d5691d5a0e/numpy-2.4.4-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:d2a8490669bfe99a233298348acc2d824d496dee0e66e31b66a6022c2ad74a5c", size = 6547539 }, - { url = "https://files.pythonhosted.org/packages/7d/90/8d23e3b0dafd024bf31bdec225b3bb5c2dbfa6912f8a53b8659f21216cbf/numpy-2.4.4-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:45dbed2ab436a9e826e302fcdcbe9133f9b0006e5af7168afb8963a6520da103", size = 15668806 }, - { url = "https://files.pythonhosted.org/packages/d1/73/a9d864e42a01896bb5974475438f16086be9ba1f0d19d0bb7a07427c4a8b/numpy-2.4.4-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c901b15172510173f5cb310eae652908340f8dede90fff9e3bf6c0d8dfd92f83", size = 16632682 }, - { url = "https://files.pythonhosted.org/packages/34/fb/14570d65c3bde4e202a031210475ae9cde9b7686a2e7dc97ee67d2833b35/numpy-2.4.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:99d838547ace2c4aace6c4f76e879ddfe02bb58a80c1549928477862b7a6d6ed", size = 17019810 }, - { url = "https://files.pythonhosted.org/packages/8a/77/2ba9d87081fd41f6d640c83f26fb7351e536b7ce6dd9061b6af5904e8e46/numpy-2.4.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:0aec54fd785890ecca25a6003fd9a5aed47ad607bbac5cd64f836ad8666f4959", size = 18357394 }, - { url = "https://files.pythonhosted.org/packages/a2/23/52666c9a41708b0853fa3b1a12c90da38c507a3074883823126d4e9d5b30/numpy-2.4.4-cp313-cp313-win32.whl", hash = "sha256:07077278157d02f65c43b1b26a3886bce886f95d20aabd11f87932750dfb14ed", size = 5959556 }, - { url = "https://files.pythonhosted.org/packages/57/fb/48649b4971cde70d817cf97a2a2fdc0b4d8308569f1dd2f2611959d2e0cf/numpy-2.4.4-cp313-cp313-win_amd64.whl", hash = "sha256:5c70f1cc1c4efbe316a572e2d8b9b9cc44e89b95f79ca3331553fbb63716e2bf", size = 12317311 }, - { url = "https://files.pythonhosted.org/packages/ba/d8/11490cddd564eb4de97b4579ef6bfe6a736cc07e94c1598590ae25415e01/numpy-2.4.4-cp313-cp313-win_arm64.whl", hash = "sha256:ef4059d6e5152fa1a39f888e344c73fdc926e1b2dd58c771d67b0acfbf2aa67d", size = 10222060 }, - { url = "https://files.pythonhosted.org/packages/99/5d/dab4339177a905aad3e2221c915b35202f1ec30d750dd2e5e9d9a72b804b/numpy-2.4.4-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:4bbc7f303d125971f60ec0aaad5e12c62d0d2c925f0ab1273debd0e4ba37aba5", size = 14822302 }, - { url = "https://files.pythonhosted.org/packages/eb/e4/0564a65e7d3d97562ed6f9b0fd0fb0a6f559ee444092f105938b50043876/numpy-2.4.4-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:4d6d57903571f86180eb98f8f0c839fa9ebbfb031356d87f1361be91e433f5b7", size = 5327407 }, - { url = "https://files.pythonhosted.org/packages/29/8d/35a3a6ce5ad371afa58b4700f1c820f8f279948cca32524e0a695b0ded83/numpy-2.4.4-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:4636de7fd195197b7535f231b5de9e4b36d2c440b6e566d2e4e4746e6af0ca93", size = 6647631 }, - { url = "https://files.pythonhosted.org/packages/f4/da/477731acbd5a58a946c736edfdabb2ac5b34c3d08d1ba1a7b437fa0884df/numpy-2.4.4-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ad2e2ef14e0b04e544ea2fa0a36463f847f113d314aa02e5b402fdf910ef309e", size = 15727691 }, - { url = "https://files.pythonhosted.org/packages/e6/db/338535d9b152beabeb511579598418ba0212ce77cf9718edd70262cc4370/numpy-2.4.4-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5a285b3b96f951841799528cd1f4f01cd70e7e0204b4abebac9463eecfcf2a40", size = 16681241 }, - { url = "https://files.pythonhosted.org/packages/e2/a9/ad248e8f58beb7a0219b413c9c7d8151c5d285f7f946c3e26695bdbbe2df/numpy-2.4.4-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:f8474c4241bc18b750be2abea9d7a9ec84f46ef861dbacf86a4f6e043401f79e", size = 17085767 }, - { url = "https://files.pythonhosted.org/packages/b5/1a/3b88ccd3694681356f70da841630e4725a7264d6a885c8d442a697e1146b/numpy-2.4.4-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:4e874c976154687c1f71715b034739b45c7711bec81db01914770373d125e392", size = 18403169 }, - { url = "https://files.pythonhosted.org/packages/c2/c9/fcfd5d0639222c6eac7f304829b04892ef51c96a75d479214d77e3ce6e33/numpy-2.4.4-cp313-cp313t-win32.whl", hash = "sha256:9c585a1790d5436a5374bac930dad6ed244c046ed91b2b2a3634eb2971d21008", size = 6083477 }, - { url = "https://files.pythonhosted.org/packages/d5/e3/3938a61d1c538aaec8ed6fd6323f57b0c2d2d2219512434c5c878db76553/numpy-2.4.4-cp313-cp313t-win_amd64.whl", hash = "sha256:93e15038125dc1e5345d9b5b68aa7f996ec33b98118d18c6ca0d0b7d6198b7e8", size = 12457487 }, - { url = "https://files.pythonhosted.org/packages/97/6a/7e345032cc60501721ef94e0e30b60f6b0bd601f9174ebd36389a2b86d40/numpy-2.4.4-cp313-cp313t-win_arm64.whl", hash = "sha256:0dfd3f9d3adbe2920b68b5cd3d51444e13a10792ec7154cd0a2f6e74d4ab3233", size = 10292002 }, - { url = "https://files.pythonhosted.org/packages/6e/06/c54062f85f673dd5c04cbe2f14c3acb8c8b95e3384869bb8cc9bff8cb9df/numpy-2.4.4-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:f169b9a863d34f5d11b8698ead99febeaa17a13ca044961aa8e2662a6c7766a0", size = 16684353 }, - { url = "https://files.pythonhosted.org/packages/4c/39/8a320264a84404c74cc7e79715de85d6130fa07a0898f67fb5cd5bd79908/numpy-2.4.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:2483e4584a1cb3092da4470b38866634bafb223cbcd551ee047633fd2584599a", size = 14704914 }, - { url = "https://files.pythonhosted.org/packages/91/fb/287076b2614e1d1044235f50f03748f31fa287e3dbe6abeb35cdfa351eca/numpy-2.4.4-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:2d19e6e2095506d1736b7d80595e0f252d76b89f5e715c35e06e937679ea7d7a", size = 5210005 }, - { url = "https://files.pythonhosted.org/packages/63/eb/fcc338595309910de6ecabfcef2419a9ce24399680bfb149421fa2df1280/numpy-2.4.4-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:6a246d5914aa1c820c9443ddcee9c02bec3e203b0c080349533fae17727dfd1b", size = 6544974 }, - { url = "https://files.pythonhosted.org/packages/44/5d/e7e9044032a716cdfaa3fba27a8e874bf1c5f1912a1ddd4ed071bf8a14a6/numpy-2.4.4-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:989824e9faf85f96ec9c7761cd8d29c531ad857bfa1daa930cba85baaecf1a9a", size = 15684591 }, - { url = "https://files.pythonhosted.org/packages/98/7c/21252050676612625449b4807d6b695b9ce8a7c9e1c197ee6216c8a65c7c/numpy-2.4.4-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:27a8d92cd10f1382a67d7cf4db7ce18341b66438bdd9f691d7b0e48d104c2a9d", size = 16637700 }, - { url = "https://files.pythonhosted.org/packages/b1/29/56d2bbef9465db24ef25393383d761a1af4f446a1df9b8cded4fe3a5a5d7/numpy-2.4.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:e44319a2953c738205bf3354537979eaa3998ed673395b964c1176083dd46252", size = 17035781 }, - { url = "https://files.pythonhosted.org/packages/e3/2b/a35a6d7589d21f44cea7d0a98de5ddcbb3d421b2622a5c96b1edf18707c3/numpy-2.4.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:e892aff75639bbef0d2a2cfd55535510df26ff92f63c92cd84ef8d4ba5a5557f", size = 18362959 }, - { url = "https://files.pythonhosted.org/packages/64/c9/d52ec581f2390e0f5f85cbfd80fb83d965fc15e9f0e1aec2195faa142cde/numpy-2.4.4-cp314-cp314-win32.whl", hash = "sha256:1378871da56ca8943c2ba674530924bb8ca40cd228358a3b5f302ad60cf875fc", size = 6008768 }, - { url = "https://files.pythonhosted.org/packages/fa/22/4cc31a62a6c7b74a8730e31a4274c5dc80e005751e277a2ce38e675e4923/numpy-2.4.4-cp314-cp314-win_amd64.whl", hash = "sha256:715d1c092715954784bc79e1174fc2a90093dc4dc84ea15eb14dad8abdcdeb74", size = 12449181 }, - { url = "https://files.pythonhosted.org/packages/70/2e/14cda6f4d8e396c612d1bf97f22958e92148801d7e4f110cabebdc0eef4b/numpy-2.4.4-cp314-cp314-win_arm64.whl", hash = "sha256:2c194dd721e54ecad9ad387c1d35e63dce5c4450c6dc7dd5611283dda239aabb", size = 10496035 }, - { url = "https://files.pythonhosted.org/packages/b1/e8/8fed8c8d848d7ecea092dc3469643f9d10bc3a134a815a3b033da1d2039b/numpy-2.4.4-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:2aa0613a5177c264ff5921051a5719d20095ea586ca88cc802c5c218d1c67d3e", size = 14824958 }, - { url = "https://files.pythonhosted.org/packages/05/1a/d8007a5138c179c2bf33ef44503e83d70434d2642877ee8fbb230e7c0548/numpy-2.4.4-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:42c16925aa5a02362f986765f9ebabf20de75cdefdca827d14315c568dcab113", size = 5330020 }, - { url = "https://files.pythonhosted.org/packages/99/64/ffb99ac6ae93faf117bcbd5c7ba48a7f45364a33e8e458545d3633615dda/numpy-2.4.4-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:874f200b2a981c647340f841730fc3a2b54c9d940566a3c4149099591e2c4c3d", size = 6650758 }, - { url = "https://files.pythonhosted.org/packages/6e/6e/795cc078b78a384052e73b2f6281ff7a700e9bf53bcce2ee579d4f6dd879/numpy-2.4.4-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c9b39d38a9bd2ae1becd7eac1303d031c5c110ad31f2b319c6e7d98b135c934d", size = 15729948 }, - { url = "https://files.pythonhosted.org/packages/5f/86/2acbda8cc2af5f3d7bfc791192863b9e3e19674da7b5e533fded124d1299/numpy-2.4.4-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b268594bccac7d7cf5844c7732e3f20c50921d94e36d7ec9b79e9857694b1b2f", size = 16679325 }, - { url = "https://files.pythonhosted.org/packages/bc/59/cafd83018f4aa55e0ac6fa92aa066c0a1877b77a615ceff1711c260ffae8/numpy-2.4.4-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:ac6b31e35612a26483e20750126d30d0941f949426974cace8e6b5c58a3657b0", size = 17084883 }, - { url = "https://files.pythonhosted.org/packages/f0/85/a42548db84e65ece46ab2caea3d3f78b416a47af387fcbb47ec28e660dc2/numpy-2.4.4-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:8e3ed142f2728df44263aaf5fb1f5b0b99f4070c553a0d7f033be65338329150", size = 18403474 }, - { url = "https://files.pythonhosted.org/packages/ed/ad/483d9e262f4b831000062e5d8a45e342166ec8aaa1195264982bca267e62/numpy-2.4.4-cp314-cp314t-win32.whl", hash = "sha256:dddbbd259598d7240b18c9d87c56a9d2fb3b02fe266f49a7c101532e78c1d871", size = 6155500 }, - { url = "https://files.pythonhosted.org/packages/c7/03/2fc4e14c7bd4ff2964b74ba90ecb8552540b6315f201df70f137faa5c589/numpy-2.4.4-cp314-cp314t-win_amd64.whl", hash = "sha256:a7164afb23be6e37ad90b2f10426149fd75aee07ca55653d2aa41e66c4ef697e", size = 12637755 }, - { url = "https://files.pythonhosted.org/packages/58/78/548fb8e07b1a341746bfbecb32f2c268470f45fa028aacdbd10d9bc73aab/numpy-2.4.4-cp314-cp314t-win_arm64.whl", hash = "sha256:ba203255017337d39f89bdd58417f03c4426f12beed0440cfd933cb15f8669c7", size = 10566643 }, - { url = "https://files.pythonhosted.org/packages/6b/33/8fae8f964a4f63ed528264ddf25d2b683d0b663e3cba26961eb838a7c1bd/numpy-2.4.4-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:58c8b5929fcb8287cbd6f0a3fae19c6e03a5c48402ae792962ac465224a629a4", size = 16854491 }, - { url = "https://files.pythonhosted.org/packages/bc/d0/1aabee441380b981cf8cdda3ae7a46aa827d1b5a8cce84d14598bc94d6d9/numpy-2.4.4-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:eea7ac5d2dce4189771cedb559c738a71512768210dc4e4753b107a2048b3d0e", size = 14895830 }, - { url = "https://files.pythonhosted.org/packages/a5/b8/aafb0d1065416894fccf4df6b49ef22b8db045187949545bced89c034b8e/numpy-2.4.4-pp311-pypy311_pp73-macosx_14_0_arm64.whl", hash = "sha256:51fc224f7ca4d92656d5a5eb315f12eb5fe2c97a66249aa7b5f562528a3be38c", size = 5400927 }, - { url = "https://files.pythonhosted.org/packages/d6/77/063baa20b08b431038c7f9ff5435540c7b7265c78cf56012a483019ca72d/numpy-2.4.4-pp311-pypy311_pp73-macosx_14_0_x86_64.whl", hash = "sha256:28a650663f7314afc3e6ec620f44f333c386aad9f6fc472030865dc0ebb26ee3", size = 6715557 }, - { url = "https://files.pythonhosted.org/packages/c7/a8/379542d45a14f149444c5c4c4e7714707239ce9cc1de8c2803958889da14/numpy-2.4.4-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:19710a9ca9992d7174e9c52f643d4272dcd1558c5f7af7f6f8190f633bd651a7", size = 15804253 }, - { url = "https://files.pythonhosted.org/packages/a2/c8/f0a45426d6d21e7ea3310a15cf90c43a14d9232c31a837702dba437f3373/numpy-2.4.4-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9b2aec6af35c113b05695ebb5749a787acd63cafc83086a05771d1e1cd1e555f", size = 16753552 }, - { url = "https://files.pythonhosted.org/packages/04/74/f4c001f4714c3ad9ce037e18cf2b9c64871a84951eaa0baf683a9ca9301c/numpy-2.4.4-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:f2cf083b324a467e1ab358c105f6cad5ea950f50524668a80c486ff1db24e119", size = 12509075 }, +version = "2.5.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.15' and sys_platform == 'win32'", + "python_full_version == '3.14.*' and sys_platform == 'win32'", + "python_full_version >= '3.15' and sys_platform == 'emscripten'", + "python_full_version == '3.14.*' and sys_platform == 'emscripten'", + "python_full_version >= '3.15' and sys_platform != 'darwin' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.14.*' and sys_platform != 'darwin' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version >= '3.15' and sys_platform == 'darwin'", + "python_full_version == '3.14.*' and sys_platform == 'darwin'", + "python_full_version == '3.13.*' and sys_platform == 'win32'", + "python_full_version == '3.12.*' and sys_platform == 'win32'", + "python_full_version == '3.13.*' and sys_platform == 'emscripten'", + "python_full_version == '3.12.*' and sys_platform == 'emscripten'", + "python_full_version == '3.13.*' and sys_platform != 'darwin' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.12.*' and sys_platform != 'darwin' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.13.*' and sys_platform == 'darwin'", + "python_full_version == '3.12.*' and sys_platform == 'darwin'", +] +sdist = { url = "https://files.pythonhosted.org/packages/22/fd/89965aa4ac08c74998539fcbf24fa3540f3e15237fbeb6bcf9c908f4aade/numpy-2.5.1.tar.gz", hash = "sha256:a48a113e6afea91f5608793bafa7ef2ad481fefbda87ec5069f483de61cb9fa3", size = 20755553, upload-time = "2026-07-04T17:08:00.933Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/62/7b/14687aa674250e5e546f616f486b0d56d3631cd5b2415739141ce40bdcea/numpy-2.5.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:2c889b56fe48b1018f764b0eec8df59ab654e9148aa91faa12596043500de277", size = 16801574, upload-time = "2026-07-04T17:06:12.423Z" }, + { url = "https://files.pythonhosted.org/packages/e1/19/cc5bb2a3f2913d27d6dbb2c78d25921fabaedc6741d4a5a615a11f3c5bf3/numpy-2.5.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ab451b59c5643c570974c43aef780703ef1d3b4965d2be07afd530615a9358d1", size = 11772250, upload-time = "2026-07-04T17:06:15.726Z" }, + { url = "https://files.pythonhosted.org/packages/42/77/fdf34a71dd30f54979b18603bee915e0aaf825b07afe79acd60b04b691e2/numpy-2.5.1-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:78798bd5b9ad744056af8efa90e3b9ddaa53272a0848a483084a1cc0a13b2dc0", size = 5331516, upload-time = "2026-07-04T17:06:17.913Z" }, + { url = "https://files.pythonhosted.org/packages/ce/e2/eb7efa015b4cce41e2517bf182a7fce0d7d5b9d9ed76a29bfa0f4fe4505c/numpy-2.5.1-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:2ae0ca40bcb22d6ba59c1dfd5446f49940b0f2d821fde133f10dda11f816b84e", size = 6664863, upload-time = "2026-07-04T17:06:20.02Z" }, + { url = "https://files.pythonhosted.org/packages/a9/4b/a2b32dd94ee9ffbeecb28152240042a3949db33b1c834d44090b80e1b3b8/numpy-2.5.1-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:61ac47e772e6b8ea489e1d2f441a34c5c3ac17327e7ce294cbdf535795ad4e75", size = 15167977, upload-time = "2026-07-04T17:06:21.621Z" }, + { url = "https://files.pythonhosted.org/packages/b8/a9/6e73d68500f80773f65f0654ea932019d6694329a0eb0ed0533de38df376/numpy-2.5.1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:59fda5e192b570217ec2580c96f00e9a7e12ef6866a900eb089b62c1a32545ca", size = 16672469, upload-time = "2026-07-04T17:06:24.064Z" }, + { url = "https://files.pythonhosted.org/packages/24/7d/ad3e59015135f5261c95fd4cafeff159c955febd83a99a1d9250c4233815/numpy-2.5.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:f7119ebff1a9829e9f431a4f9d28e703023bb6b9fe7c8f724467dbfc27c94ab3", size = 16527531, upload-time = "2026-07-04T17:06:26.69Z" }, + { url = "https://files.pythonhosted.org/packages/83/d0/a39b2fbcde9cb17a1dac678f254b33a6336298af9df338824c685425d5e8/numpy-2.5.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e824c2acf8862052246be5a44c15da1777940c60d010dd2aab897824d9c430f9", size = 18431940, upload-time = "2026-07-04T17:06:29.521Z" }, + { url = "https://files.pythonhosted.org/packages/04/12/cff070947791c1ed425ff76413189adbdc2fbe215eba7ce7fa454a03c7f8/numpy-2.5.1-cp312-cp312-win32.whl", hash = "sha256:08d60c810432eb83360958dea0999ac4cfb94531ea8efcbf0b7f277c2068aeb2", size = 6066764, upload-time = "2026-07-04T17:06:32.571Z" }, + { url = "https://files.pythonhosted.org/packages/65/66/53f31807a48a750f9d748da273bc3fcedd12b27ff1f3e373bfec55ef2dc0/numpy-2.5.1-cp312-cp312-win_amd64.whl", hash = "sha256:f7d60026c0bdb1380e83bfa7a0419c4577ee4b9a08880afcb6dadeb74c649fa2", size = 12430966, upload-time = "2026-07-04T17:06:34.926Z" }, + { url = "https://files.pythonhosted.org/packages/2b/2a/d1a88066b1c14186f5d3c0d18c94f17b064511982bab0578d49ee9d43c29/numpy-2.5.1-cp312-cp312-win_arm64.whl", hash = "sha256:17a25e09640602e10bc8de0e6fa2b3fd68eedd84ba6d7842dc8f32f9ab87bd0b", size = 10350488, upload-time = "2026-07-04T17:06:37.785Z" }, + { url = "https://files.pythonhosted.org/packages/eb/07/ec2a3f0c91761581d4b7104a740791800025983f9a4dc4e73f91a99aeac4/numpy-2.5.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0bfebd8695f9863592fe744be833a258120b14a9f39da255e8aa8fade2c0ddd1", size = 16796419, upload-time = "2026-07-04T17:06:40.37Z" }, + { url = "https://files.pythonhosted.org/packages/ab/ab/ddb499fc4f8780354395face5b65c7fd107bcd6e1d667a5f07d046956f6f/numpy-2.5.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:30b44a6b53a7ae63c54c089a8726e5563ed302716c5b7ccc85afade40b0e7ff6", size = 11765832, upload-time = "2026-07-04T17:06:42.768Z" }, + { url = "https://files.pythonhosted.org/packages/88/b3/3c28c558a09fc72100c646dac6d2fce8e834c471b0edca01a29996706117/numpy-2.5.1-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:6165343f81b56ef8f514f396989e529b61d9dc709b99421b07e9f3e698e2287d", size = 5325143, upload-time = "2026-07-04T17:06:45.466Z" }, + { url = "https://files.pythonhosted.org/packages/5e/0e/ce19b985bb15c596f4f05954e76cccc77c845083b3b8f938a6c68e523128/numpy-2.5.1-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:4939237038ada79308dda3204ac6462df056b5672b2e25db1149cf873668b3e1", size = 6659749, upload-time = "2026-07-04T17:06:47.288Z" }, + { url = "https://files.pythonhosted.org/packages/2e/20/1ee6614d64332a1bba6411f38e68cb79eec1b2459e20a623777c5c5492a2/numpy-2.5.1-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1c6759f538fb912fc46de0a6b1758ccf7b57bc7c7ebebc23974fdac3de8db0cd", size = 15164716, upload-time = "2026-07-04T17:06:49.494Z" }, + { url = "https://files.pythonhosted.org/packages/ed/a7/2bcd3fdbb87804755c35b729bf8709d62025c5f4cfd7d5b2415997097515/numpy-2.5.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9726558e8db4a5bf7929a70ae50f63abda4daf0efe810e3bfbab95976f75fc1a", size = 16661440, upload-time = "2026-07-04T17:06:52.061Z" }, + { url = "https://files.pythonhosted.org/packages/fc/d7/a41e3310c886fe457d36e670bbf24fae411aca8a7b6ad92a32afd924077c/numpy-2.5.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:3935f3b419b244a02732676fa5317a9193cc596a4c0646db07e5b421229ac9f7", size = 16526305, upload-time = "2026-07-04T17:06:54.605Z" }, + { url = "https://files.pythonhosted.org/packages/53/75/4333a9a707c1edd3a4e1a0c58eca52c0f31e55089fa80db02b5565b24df7/numpy-2.5.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:dc932a65ded7ce9013d120845a2514dcccb1a67bfc8deb8d37633762951904a6", size = 18423008, upload-time = "2026-07-04T17:06:57.54Z" }, + { url = "https://files.pythonhosted.org/packages/ee/90/e314a32b1c11a2ffe818ddad3a57b50b4b6e1b6c487192eb50cdef0415d0/numpy-2.5.1-cp313-cp313-win32.whl", hash = "sha256:4b4ff1608417eb7a59da7b967bbb798cacfe071d2caf526a24281cd562072ed9", size = 6063885, upload-time = "2026-07-04T17:07:00.14Z" }, + { url = "https://files.pythonhosted.org/packages/10/70/800b3fca480af32df9e8ea9f3d4a0c8feb4b32d7f195d174eabbda4829ad/numpy-2.5.1-cp313-cp313-win_amd64.whl", hash = "sha256:6c3fe51bc6a16453d452997053454f309e8e0ed7b42d6b361ce4ac8c32913d74", size = 12425674, upload-time = "2026-07-04T17:07:02.387Z" }, + { url = "https://files.pythonhosted.org/packages/8b/0b/196350c122f50f6ca56846f2d71efd5e0d24b7b2e07355e019b2e2c7a11e/numpy-2.5.1-cp313-cp313-win_arm64.whl", hash = "sha256:f7feb014281029e628ba2d5a007407443b06e418b6fe451d1e2adcbc8eba0107", size = 10350256, upload-time = "2026-07-04T17:07:04.878Z" }, + { url = "https://files.pythonhosted.org/packages/db/f4/731b6085a83faf6ca843394cbd5e217280c214399f7e8b21b9f552af0ae2/numpy-2.5.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:7c786fe9a5bbe360022e584c5a34cf6b54265c71bd7ec8ac3d8fec38968071f8", size = 16795063, upload-time = "2026-07-04T17:07:07.374Z" }, + { url = "https://files.pythonhosted.org/packages/bf/64/0e215f2048dd11a55bb989ed41b3585ef57452404e638d703a211a3e4157/numpy-2.5.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:32985c896d897419ef8da6917872d80b78ad0ea26d85b23245c7366ffde76d75", size = 11776652, upload-time = "2026-07-04T17:07:09.907Z" }, + { url = "https://files.pythonhosted.org/packages/b5/59/2b844c7a6e9deff69b404a66221e1542937734f65d5e6e39411876053862/numpy-2.5.1-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:efd736408cc97c79b9e6917338dfc8f06013b2274f992e96b1d9a81a71e2a2c2", size = 5335944, upload-time = "2026-07-04T17:07:12.227Z" }, + { url = "https://files.pythonhosted.org/packages/86/51/9bf7cb2cabcebc9e017e4ec7e6322b378317a542c08b4cb68479c1efc716/numpy-2.5.1-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:ab84dc6b074fa881cae55bea94cc4f68e285181ba7f32497bf7dee6b1496165b", size = 6656266, upload-time = "2026-07-04T17:07:14.368Z" }, + { url = "https://files.pythonhosted.org/packages/83/3e/fb7615b211b82a32f44d5180a6d421b61f84d4fadd578b48ba4ac34e189f/numpy-2.5.1-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:caf3e317d33d60c37986b452613f4ab51246d0691350c03d0cb4a898627f4a95", size = 15179720, upload-time = "2026-07-04T17:07:16.272Z" }, + { url = "https://files.pythonhosted.org/packages/41/5f/0f992cb24560673496c5d68de61913b57166ce530ffda07c1f280e0cc464/numpy-2.5.1-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:54ad769f17bc2d833b620851989f62054fb9ab93c969d9e1dc3c8e3d56beea21", size = 16664835, upload-time = "2026-07-04T17:07:19.021Z" }, + { url = "https://files.pythonhosted.org/packages/a2/2f/97d6475ee91afe2587797d09446f9d3e475ad4cb681662d824809327b75a/numpy-2.5.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c12afb53450fa976d4c681c50a7423729a4c51c0465ed9f32b8a9cabbc472373", size = 16539135, upload-time = "2026-07-04T17:07:22.015Z" }, + { url = "https://files.pythonhosted.org/packages/c4/5b/4db81e4ba0be7e2776b1de68c82aa862c7f8ec27e1b4927d4ae075e20678/numpy-2.5.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:e8c11c405efc5ff6816d5983c96cdfa215bab3428961243af3ff59b228490438", size = 18426684, upload-time = "2026-07-04T17:07:24.941Z" }, + { url = "https://files.pythonhosted.org/packages/1f/64/c0ba2d90724d450279a7df8f32057241070250a26a7e2b5337d77347f481/numpy-2.5.1-cp314-cp314-win32.whl", hash = "sha256:f2479a47f8d5932d1718168a681ad6e536a9df484c83cfcf9de365e164537ace", size = 6116103, upload-time = "2026-07-04T17:07:27.622Z" }, + { url = "https://files.pythonhosted.org/packages/c1/1a/837f9ed7405adcd7a40538792eb169eddd8fa5630c16a1ef49dae71a30f4/numpy-2.5.1-cp314-cp314-win_amd64.whl", hash = "sha256:24d0eb82c0541d3415a33425db64ae439dffccd7b4dbcb30e7c35120205c506a", size = 12562177, upload-time = "2026-07-04T17:07:29.887Z" }, + { url = "https://files.pythonhosted.org/packages/22/ed/49707938b6dd0a78a9178dd93227dc89e4c11af47f5c798d70366e8d0483/numpy-2.5.1-cp314-cp314-win_arm64.whl", hash = "sha256:5a4c988b38d261deeeaad9954e3deb091ad905c94e8bb6708654ef1d97f286b0", size = 10627739, upload-time = "2026-07-04T17:07:32.568Z" }, + { url = "https://files.pythonhosted.org/packages/a6/c7/bb4b882cfe7f299cbc8b66e42e7dd78cf9d14e40f9469fc5e3db7e15b3bd/numpy-2.5.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:a33276be12fa045805f477f22482088b66bb758ffbe89a9d21457de863a32e22", size = 11894709, upload-time = "2026-07-04T17:07:34.941Z" }, + { url = "https://files.pythonhosted.org/packages/40/3f/5af7f4a7f6224aef48017aa82bb6174c7a659d724be0c75017b7e64a55b4/numpy-2.5.1-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:f089d7b00756190aacf1f5d34bdf38c3c430ac82b4f868f8cede73380460fce7", size = 5453810, upload-time = "2026-07-04T17:07:37.495Z" }, + { url = "https://files.pythonhosted.org/packages/20/c9/3474309bc94d634d3f9c3eddf03250ecb8c22cd948ef16fef69a77cc5d7b/numpy-2.5.1-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:09e9bfd8d2cf479c7d174804fb3811c53a8e9f20a37444008606b57d6b7a826d", size = 6761189, upload-time = "2026-07-04T17:07:39.563Z" }, + { url = "https://files.pythonhosted.org/packages/90/8a/558ae39fdd55d7e7f7fef9a84a6e964ac6b23edbd2a07e52bb084500507d/numpy-2.5.1-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e68d8dd1e7eba712948f2053a29ec86917bc70ba1358df869d9f06649ef9cf09", size = 15225039, upload-time = "2026-07-04T17:07:41.682Z" }, + { url = "https://files.pythonhosted.org/packages/63/27/ca7392b2d030277bdf0273e7d23255b3ee57d57a7c170a6f4fb3981e1e5d/numpy-2.5.1-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:99d5095fa265a0c4152e7bb12759e14381ef5496152f1ce58f44bdf55c44beb4", size = 16701306, upload-time = "2026-07-04T17:07:44.611Z" }, + { url = "https://files.pythonhosted.org/packages/02/42/03d53ae7996c44d4374a8262e9dc41671fd56cbb98f7d47ef85cf5da4c6b/numpy-2.5.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:ab87a91b3cc3382b8956095bd8f95e00cf679bb81554339be1a2ba404a1473c1", size = 16589955, upload-time = "2026-07-04T17:07:47.694Z" }, + { url = "https://files.pythonhosted.org/packages/7b/15/6c1784ae469640e65db111e9a34b3d0f14d91e8a38b9ce34810ced370dbb/numpy-2.5.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:224ca51130ef7da85bea2191625181cb4f337f9cb64b471f10c1a12aa8b60077", size = 18464252, upload-time = "2026-07-04T17:07:50.684Z" }, + { url = "https://files.pythonhosted.org/packages/94/a8/f98e50356cf167df656c526c2dfeec2d7dde182f2a3da4b458a5938e2776/numpy-2.5.1-cp314-cp314t-win32.whl", hash = "sha256:6eab239876581b2b3c5a242281b6007bbdbcd1c7085d7709bb57c5929b11e6bf", size = 6263298, upload-time = "2026-07-04T17:07:53.445Z" }, + { url = "https://files.pythonhosted.org/packages/72/ac/96ae880cdecad0b3275d9359fcec72667b49a4863c9f12942e43679dda02/numpy-2.5.1-cp314-cp314t-win_amd64.whl", hash = "sha256:83ce9c80d5b521b0d77ddcbe5447c218d247929b6cc056ca5351342accfff0af", size = 12748623, upload-time = "2026-07-04T17:07:55.384Z" }, + { url = "https://files.pythonhosted.org/packages/a1/5a/4d2b1601df3602dba7a14f3348ba9bfe94a18adb428e693df6154c293831/numpy-2.5.1-cp314-cp314t-win_arm64.whl", hash = "sha256:5a6db61f9aaa57e369905c67d852045d3c4f7126405b29d09b19dec118e9c9cb", size = 10697674, upload-time = "2026-07-04T17:07:58.506Z" }, ] [[package]] name = "opencv-python-headless" -version = "4.13.0.92" +version = "5.0.0.93" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "numpy" }, + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, + { name = "numpy", version = "2.5.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, ] +sdist = { url = "https://files.pythonhosted.org/packages/1d/99/76b7c80252aa83c1af16393454aafd125a0287101afe8deb0a6821af0e30/opencv_python_headless-5.0.0.93.tar.gz", hash = "sha256:b82f9831daab90b725c7c1ee1b36cb5732c367096ac76d119e64e14eb70d5f3c", size = 81817738, upload-time = "2026-07-02T07:01:06.039Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/79/42/2310883be3b8826ac58c3f2787b9358a2d46923d61f88fedf930bc59c60c/opencv_python_headless-4.13.0.92-cp37-abi3-macosx_13_0_arm64.whl", hash = "sha256:1a7d040ac656c11b8c38677cc8cccdc149f98535089dbe5b081e80a4e5903209", size = 46247192 }, - { url = "https://files.pythonhosted.org/packages/2d/1e/6f9e38005a6f7f22af785df42a43139d0e20f169eb5787ce8be37ee7fcc9/opencv_python_headless-4.13.0.92-cp37-abi3-macosx_14_0_x86_64.whl", hash = "sha256:3e0a6f0a37994ec6ce5f59e936be21d5d6384a4556f2d2da9c2f9c5dc948394c", size = 32568914 }, - { url = "https://files.pythonhosted.org/packages/21/76/9417a6aef9def70e467a5bf560579f816148a4c658b7d525581b356eda9e/opencv_python_headless-4.13.0.92-cp37-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:5c8cfc8e87ed452b5cecb9419473ee5560a989859fe1d10d1ce11ae87b09a2cb", size = 33703709 }, - { url = "https://files.pythonhosted.org/packages/92/ce/bd17ff5772938267fd49716e94ca24f616ff4cb1ff4c6be13085108037be/opencv_python_headless-4.13.0.92-cp37-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:0525a3d2c0b46c611e2130b5fdebc94cf404845d8fa64d2f3a3b679572a5bd22", size = 56016764 }, - { url = "https://files.pythonhosted.org/packages/8f/b4/b7bcbf7c874665825a8c8e1097e93ea25d1f1d210a3e20d4451d01da30aa/opencv_python_headless-4.13.0.92-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:eb60e36b237b1ebd40a912da5384b348df8ed534f6f644d8e0b4f103e272ba7d", size = 35010236 }, - { url = "https://files.pythonhosted.org/packages/4b/33/b5db29a6c00eb8f50708110d8d453747ca125c8b805bc437b289dbdcc057/opencv_python_headless-4.13.0.92-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:0bd48544f77c68b2941392fcdf9bcd2b9cdf00e98cb8c29b2455d194763cf99e", size = 60391106 }, - { url = "https://files.pythonhosted.org/packages/fb/c3/52cfea47cd33e53e8c0fbd6e7c800b457245c1fda7d61660b4ffe9596a7f/opencv_python_headless-4.13.0.92-cp37-abi3-win32.whl", hash = "sha256:a7cf08e5b191f4ebb530791acc0825a7986e0d0dee2a3c491184bd8599848a4b", size = 30812232 }, - { url = "https://files.pythonhosted.org/packages/4a/90/b338326131ccb2aaa3c2c85d00f41822c0050139a4bfe723cfd95455bd2d/opencv_python_headless-4.13.0.92-cp37-abi3-win_amd64.whl", hash = "sha256:77a82fe35ddcec0f62c15f2ba8a12ecc2ed4207c17b0902c7a3151ae29f37fb6", size = 40070414 }, + { url = "https://files.pythonhosted.org/packages/53/7c/8c8097891c509d98cd128493835c95631c80be6a8f37ed9d25716c2e16f1/opencv_python_headless-5.0.0.93-cp37-abi3-macosx_13_0_arm64.whl", hash = "sha256:030ca5e0837a2963ab36ef896baa9767eb8d2b83353fb28af5a521e40dd8756f", size = 48322581, upload-time = "2026-07-02T05:50:34.207Z" }, + { url = "https://files.pythonhosted.org/packages/90/8c/eab2ad388c3cbab2a350c10c2ef19ce6bd099240afc31789032c996bab52/opencv_python_headless-5.0.0.93-cp37-abi3-macosx_14_0_x86_64.whl", hash = "sha256:1e55af3abfb462eeeabe5c775f12bdb36216d8a93a3583d69e6bd6e1d6ba7d00", size = 34782894, upload-time = "2026-07-02T05:51:39.856Z" }, + { url = "https://files.pythonhosted.org/packages/ec/78/afca939f40ffe2b2380bfa86f812b2f7d4acc5a27b27dc41b49cad7ce7b4/opencv_python_headless-5.0.0.93-cp37-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:10818d91510e05c04568ae12b5cd120779c70c01bf897b001a6221fe430df80f", size = 36521085, upload-time = "2026-07-02T06:55:24.429Z" }, + { url = "https://files.pythonhosted.org/packages/2b/97/8170e9819764c47e436c130d3ff6cfb73b58f923eae9d3a03d8982b04aec/opencv_python_headless-5.0.0.93-cp37-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:09a872a157c1376ab922a69bbf22f9a95bcc7b658a9d8b436a60212b02b2eeb4", size = 56563598, upload-time = "2026-07-02T06:55:47.355Z" }, + { url = "https://files.pythonhosted.org/packages/3a/98/1a28a7101e31801042b3098871a74b76c61581d328ef40774ff4edb53a56/opencv_python_headless-5.0.0.93-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:840bd717c21e5c11cadadc022a823315ea417f961213d06b4df010e019eb16f4", size = 39648433, upload-time = "2026-07-02T06:56:04.255Z" }, + { url = "https://files.pythonhosted.org/packages/9b/21/f6ef335f6e65724aa78b8d792b48d40a48c381715f1e62f5a5049e09d07e/opencv_python_headless-5.0.0.93-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:ed709fdf9aa0bd1f2ed8549e71d19449b03a675bb581eb292285f6861953be37", size = 61204038, upload-time = "2026-07-02T06:56:41.823Z" }, + { url = "https://files.pythonhosted.org/packages/d0/8f/b8756467ea991449a293797f6b3fa80fcfdd29598a0a60d1cd5715b96e61/opencv_python_headless-5.0.0.93-cp37-abi3-win32.whl", hash = "sha256:c6bcd96b185975ea240d22cfdb15a1f6d080cc95264cfbe2621f21bb144d89b9", size = 35411237, upload-time = "2026-07-02T05:50:12.901Z" }, + { url = "https://files.pythonhosted.org/packages/b8/88/763b967f7efd7226b82c9fae16d560cba049b1f0c036647e65c610fd636e/opencv_python_headless-5.0.0.93-cp37-abi3-win_amd64.whl", hash = "sha256:829717b6a95554f273e49e357cee3b3a2a26b6f4842fbc1bed2b45bdd8f87e0e", size = 43825962, upload-time = "2026-07-02T05:50:09.627Z" }, ] [[package]] name = "opt-einsum" version = "3.4.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/8c/b9/2ac072041e899a52f20cf9510850ff58295003aa75525e58343591b0cbfb/opt_einsum-3.4.0.tar.gz", hash = "sha256:96ca72f1b886d148241348783498194c577fa30a8faac108586b14f1ba4473ac", size = 63004 } +sdist = { url = "https://files.pythonhosted.org/packages/8c/b9/2ac072041e899a52f20cf9510850ff58295003aa75525e58343591b0cbfb/opt_einsum-3.4.0.tar.gz", hash = "sha256:96ca72f1b886d148241348783498194c577fa30a8faac108586b14f1ba4473ac", size = 63004, upload-time = "2024-09-26T14:33:24.483Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/23/cd/066e86230ae37ed0be70aae89aabf03ca8d9f39c8aea0dec8029455b5540/opt_einsum-3.4.0-py3-none-any.whl", hash = "sha256:69bb92469f86a1565195ece4ac0323943e83477171b91d24c35afe028a90d7cd", size = 71932 }, + { url = "https://files.pythonhosted.org/packages/23/cd/066e86230ae37ed0be70aae89aabf03ca8d9f39c8aea0dec8029455b5540/opt_einsum-3.4.0-py3-none-any.whl", hash = "sha256:69bb92469f86a1565195ece4ac0323943e83477171b91d24c35afe028a90d7cd", size = 71932, upload-time = "2024-09-26T14:33:23.039Z" }, ] [[package]] name = "optree" -version = "0.19.0" +version = "0.19.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/3d/63/7b078bc36d5a206c21b03565a818ede38ff0fbf014e92085ec467ef10adb/optree-0.19.0.tar.gz", hash = "sha256:bc1991a948590756409e76be4e29efd4a487a185056d35db6c67619c19ea27a1", size = 175199 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ba/5e/5967a15ed63d98717815a51265bd73fb5ec5a4ca9938c59be909ef12f66f/optree-0.19.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:e245b03f4edc90971dcf01691775105084f433393e6ba19a6fd0f151d5f39b58", size = 408515 }, - { url = "https://files.pythonhosted.org/packages/43/5b/4e062008bfd2f836079afec06dd9cba76a01010aaffa8eae0d1915fa0ef6/optree-0.19.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:304ebd0449ace6ab0c18489baea1163a915b9aeb43a8635c6373ec09767fa1ba", size = 378935 }, - { url = "https://files.pythonhosted.org/packages/bf/da/67424dad1834ba4bfe1ca16c1ca754e5fd32d7f8e7a488a70346c1bf1160/optree-0.19.0-cp311-cp311-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:48366b810f0d37e2fa7bcb758d2a3198d62f0a4a60ac7e14287768345a4a95a9", size = 400165 }, - { url = "https://files.pythonhosted.org/packages/65/af/f49c516de0ca47ce6a7a8f74499e63ba0a07744eae78958a2e6de0902fd6/optree-0.19.0-cp311-cp311-manylinux_2_26_i686.manylinux_2_28_i686.whl", hash = "sha256:b511556fdc5a558ccd65f841710e9103f120cb575735f87ddfa3728b4098e606", size = 460863 }, - { url = "https://files.pythonhosted.org/packages/92/cb/01e0a0551d6934f1158a7f90f690b4a68c1a08ba519ea4ab113e4064e499/optree-0.19.0-cp311-cp311-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ba4925025a47b12237ff8acdcf8b9e972e7f36acafb291373d4de318a4b1b12c", size = 459863 }, - { url = "https://files.pythonhosted.org/packages/b2/65/16b9c07e894a51e807404855e717e7e8221ce55f7fec3df46e9dc67c378c/optree-0.19.0-cp311-cp311-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a8d873ae944fb7d11797bb2ea2666379e78a4608e918d69a4c97fef4779f7da4", size = 459552 }, - { url = "https://files.pythonhosted.org/packages/c3/34/3c9e11011c73457dc00e031713df6822eb0690cb849f479796073e070a56/optree-0.19.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d6437d23f2708fe0fcdba9d3d75a1b82022caf2c15248059b8bf7e563422fb71", size = 441096 }, - { url = "https://files.pythonhosted.org/packages/9c/39/0932e49b76fc3354a9c39c8bb57b04661e83b28fa6c74be09b520d98915b/optree-0.19.0-cp311-cp311-manylinux_2_39_riscv64.whl", hash = "sha256:c2e01d07b2368f25932d5c6071d270a1d4db9e6cb2014eaafeded3d4c73ee406", size = 404747 }, - { url = "https://files.pythonhosted.org/packages/ef/d4/11ba9195b65bd77f8955bad5aca8c5fbfb0b273e52c4e7b29b3416c587f0/optree-0.19.0-cp311-cp311-win32.whl", hash = "sha256:846d07372ebc2ca959e9660b28d7c59ae34a02aac2fb73d0f3ec3de79af2fcb0", size = 306775 }, - { url = "https://files.pythonhosted.org/packages/d9/13/ee6a65060edde25517151eff8a9e1468e6fd65054fabd14a441161eeafd6/optree-0.19.0-cp311-cp311-win_amd64.whl", hash = "sha256:29d757b394641703098e1f9a62203c46a5f5cfa260cb8546bd05c8a2cdaf8754", size = 331592 }, - { url = "https://files.pythonhosted.org/packages/cf/5e/3a4a66d0ecee599b948d2ef17010fcb9c111c69753a3911e4301005169a8/optree-0.19.0-cp311-cp311-win_arm64.whl", hash = "sha256:fd425b501420b437d5e925708898fae927660fd433836ac282adb20d54561c63", size = 343392 }, - { url = "https://files.pythonhosted.org/packages/2d/bf/5cbbf61a27f94797c3d9786f6230223023a943b60f5e893d52368f10b8b1/optree-0.19.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7ec4b2ce49622c6be2c8634712b6c63cc274835bac89a56e3ab2ca863a32ff4b", size = 418100 }, - { url = "https://files.pythonhosted.org/packages/00/9e/65899e6470f5df289ccdbe9e228fb0cd0ae45ccda8e32c92d6efae1530ef/optree-0.19.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f0978603623b4b1f794f05f6bbed0645cb7e219f4a5a349b2a2bd4514d84ac82", size = 388582 }, - { url = "https://files.pythonhosted.org/packages/d1/dc/f4826835be660181f1b4444ac92b51dda96d4634d3c2271e14598da7bf2a/optree-0.19.0-cp312-cp312-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8c9e52c50ed3f3f8b1cf4e47a20a7c5e77175b4f84b2ecf390a76f0d1dd91da6", size = 407457 }, - { url = "https://files.pythonhosted.org/packages/ce/b0/89283ac1dd1ead3aa3d7a6b45a26846f457bded79a83b6828fc1ed9a6db3/optree-0.19.0-cp312-cp312-manylinux_2_26_i686.manylinux_2_28_i686.whl", hash = "sha256:3fe3e5f7a30a7d08ddba0a34e48f5483f6c4d7bb710375434ad3633170c73c48", size = 471230 }, - { url = "https://files.pythonhosted.org/packages/2a/a2/47f620f87b0544b2e0eb0b3c661682bd0ea1c79f6e38f9147bc0f835c973/optree-0.19.0-cp312-cp312-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8315527e1f14a91173fe6871847da7b949048ec61ff8b3e507fc286e75b0aa3c", size = 469442 }, - { url = "https://files.pythonhosted.org/packages/84/e9/b9ae18404135de53809fb994b754ac0eac838d8c4dfa8a10a811d8dec91d/optree-0.19.0-cp312-cp312-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:938fb15d140ab65148f4e6975048facbef83a9210353fbedd471ac39e7544339", size = 468840 }, - { url = "https://files.pythonhosted.org/packages/0a/e5/a77df15a62b37bb14c81b5757e2a0573f57e7c06d125a410ad2cd7cefb72/optree-0.19.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2b8209570340135a7e586c90f393f3c6359e8a49c40d783196721cc487e51d9c", size = 451408 }, - { url = "https://files.pythonhosted.org/packages/8c/43/1aa431cee19cd98c4229e468767021f9a92195d9431857e28198a3a3ce2f/optree-0.19.0-cp312-cp312-manylinux_2_39_riscv64.whl", hash = "sha256:1397dc925026917531a43fda32054ae1e77e5ed9bf8284bcae6354c19c26e14a", size = 412544 }, - { url = "https://files.pythonhosted.org/packages/5b/b9/b94fd3a116b80951d692a82f4135ae84b3d78bd1b092250aff76a3366138/optree-0.19.0-cp312-cp312-win32.whl", hash = "sha256:68f58e8f8b75c76c51e61e3dc2d9e94609bafb0e1a6459e6d525ced905cd9a74", size = 312033 }, - { url = "https://files.pythonhosted.org/packages/9e/7f/31fa1b2311038bfc355ad6e4e4e63d028719cb67fb3ebe6fb76ff2124105/optree-0.19.0-cp312-cp312-win_amd64.whl", hash = "sha256:5c44ca0f579ed3e0ca777a5711d4a6c1b374feacf1bb4fe9cfe85297b0c8d237", size = 335374 }, - { url = "https://files.pythonhosted.org/packages/09/86/863bc3f42f83113f5c6a5beaf4fec3c3481a76872f3244d0e64fb9ebd3b0/optree-0.19.0-cp312-cp312-win_arm64.whl", hash = "sha256:0461f796b4ade3fab519d821b0fa521f07e2af70206b76aac75fcfdc2e051fca", size = 345868 }, - { url = "https://files.pythonhosted.org/packages/ee/61/d79c7eeb87e98d08bc8d95ed08dee83bedb4e55371a7d2ae3c874ec02608/optree-0.19.0-cp313-cp313-android_24_arm64_v8a.whl", hash = "sha256:1eea5b7be833c6d555d08ff68046d3dd2112dfb39e6f1eb09887ab6c617a6d64", size = 923043 }, - { url = "https://files.pythonhosted.org/packages/2d/ed/e80504f65e7e80fdcd129258428d7976ea9f03bf9dad56a5293c44d563ad/optree-0.19.0-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:4d9cf9dfa0ac051e0ed82869d782f0affdbdb1daa5f2e851d37ea8625c60071a", size = 385597 }, - { url = "https://files.pythonhosted.org/packages/65/e5/d1926a2f0e0240f6800ff385c8486879f7da0a5a030b7aa5d84e44e9c9ca/optree-0.19.0-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:43c4f8ba5755d56d046be2cb1380cbc362234ad93fd9933384c6dd7fdebe6c4a", size = 392265 }, - { url = "https://files.pythonhosted.org/packages/61/88/9c598325e89bbed29b37a381ebb2b94f1d9d769c973b879b3e9766b4b16d/optree-0.19.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:36b1134680ee3f9768ede290da653e1604a8083bce69fef8fb4e46863346d5c8", size = 423763 }, - { url = "https://files.pythonhosted.org/packages/6b/d2/fcba2a1826d362a64cb36ec9f675ed6dcddee47099948913122b0aafbe44/optree-0.19.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:c9f7e7e7bf2ef011d0be1c2e87c96f5dc543dad1ac34430c2f606938c9ec5135", size = 392720 }, - { url = "https://files.pythonhosted.org/packages/eb/43/5e6d51d8c203a79cff084efa9f04a745b8ef5cf4c86dbb127e7b192f14d9/optree-0.19.0-cp313-cp313-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bb5752f17afa017b08b0cbac8a383d4bb90035b353bef7a25fe03cda69a21d33", size = 411481 }, - { url = "https://files.pythonhosted.org/packages/4b/dc/dc09347136876287b463b8599239d6fa338298fd322ac629817bd2f4def4/optree-0.19.0-cp313-cp313-manylinux_2_26_i686.manylinux_2_28_i686.whl", hash = "sha256:e9b6245993494b1aa54529eb7356aeefa6704c8b436e6e5f20b25c30f7af7620", size = 476695 }, - { url = "https://files.pythonhosted.org/packages/ee/cc/5d2c9cf906bd3ae357e7221450bacefd0321d7b94e6171dec39552b346e6/optree-0.19.0-cp313-cp313-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7351a24b30568c963a92b19f543c9562b36b3222caed2a5ac3209ef910972bec", size = 471846 }, - { url = "https://files.pythonhosted.org/packages/64/7f/75b10f88da994fc3da3dc1ab7d54bab7bd3a6fa5eb81b586f13f8bd6ab0e/optree-0.19.0-cp313-cp313-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2c6610a1d1d74af0f53c9bbabb7c265679a9a07e03783c8cc4a678ba3bb6f9a5", size = 473145 }, - { url = "https://files.pythonhosted.org/packages/78/fc/753bf69b907652d54b7c6012ccb320d8c1a3161454e415331058b6f04246/optree-0.19.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:37e07a5233be64329cbf41e20ab07c50da53bdc374109a2b376be49c4a34a37f", size = 456160 }, - { url = "https://files.pythonhosted.org/packages/e2/a8/70640f9998438f50a0a1c57f2a12aac856cd937f2c4c4feef5a3cfe8e9c7/optree-0.19.0-cp313-cp313-manylinux_2_39_riscv64.whl", hash = "sha256:c23a25caff6b096b62379adb99e2c401805141497ebb8131f271a4c93f5ed5dc", size = 417116 }, - { url = "https://files.pythonhosted.org/packages/ad/05/0b8bf4abf5d1a7cd9a19ba680e1ec64ad38eec3204e4e16a769e8aeaa4a2/optree-0.19.0-cp313-cp313-win32.whl", hash = "sha256:045cf112adaebc76c9c7cabde857c01babfc9fae8aa0a28d48f7c565fadf0cb9", size = 312101 }, - { url = "https://files.pythonhosted.org/packages/b1/c7/9ce83f115d7f4a47741827a037067b9026c29996ad7913bc40277924c773/optree-0.19.0-cp313-cp313-win_amd64.whl", hash = "sha256:bc0c6c9f99fb90e3a20a8b94c219e6b03e585f65ab9a11c9acd1511a5f885f79", size = 337944 }, - { url = "https://files.pythonhosted.org/packages/17/fd/97c27d6e51c8b958b29f5c7b4cdcae4f2e7c9ef5b5465be459811a48876b/optree-0.19.0-cp313-cp313-win_arm64.whl", hash = "sha256:48f492363fa0f9ffe5029d0ecafd2fa30ffe0d5d52c8dd414123f47b743bd42e", size = 347153 }, - { url = "https://files.pythonhosted.org/packages/46/45/9a2f05b5d033482b58ca36df6f41b0b28af3ccfa43267a82254c973dcd14/optree-0.19.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:d6362b9e9a0f4dd7c5b88debe182a90541aba7f1ad02d00922d01c4df4b3c933", size = 463985 }, - { url = "https://files.pythonhosted.org/packages/20/b7/5d0a013c5461e0933ce7385a06eed625358de12216c80da935138e6af205/optree-0.19.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:381096a293d385fd3135e5c707bb7e58c584bc9bd50f458237b49da21a621df3", size = 431307 }, - { url = "https://files.pythonhosted.org/packages/d6/2c/d3f2674411c8e3338e91e7446af239597ae6efd23f14e2039f29ced3d73e/optree-0.19.0-cp313-cp313t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a9675007cc54371be544bb33fd7eb07b0773d88deacf8aa4cc72fa735c4a4d33", size = 426917 }, - { url = "https://files.pythonhosted.org/packages/e9/e9/009964734f19d6996291e77f2c1da5d35a743defc4e89aefb01260e2f9d6/optree-0.19.0-cp313-cp313t-manylinux_2_26_i686.manylinux_2_28_i686.whl", hash = "sha256:406b355d6f29f99535efa97ea16eda70414968271a894c99f48cd91848723706", size = 490603 }, - { url = "https://files.pythonhosted.org/packages/2b/4c/96706f855c6b623259e754f751020acfb3452e412f7c85330629ab4b9ecc/optree-0.19.0-cp313-cp313t-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:d05e5bf6ce30258cda643ea50cc424038e5107905e9fc11d19a04453a8d2ee27", size = 486388 }, - { url = "https://files.pythonhosted.org/packages/b5/e4/9b23a27c9bd211d22a2e55a5a66e62afe5c75ff98b81fc7d000d879e75e6/optree-0.19.0-cp313-cp313t-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b6e11479d98690fc9efd15d65195af37608269bb1e176b5a836b066440f9c52f", size = 489090 }, - { url = "https://files.pythonhosted.org/packages/15/3b/462582f0050508f1ce0734f1dffd19078fb013fa12ccf0761c208ab6f756/optree-0.19.0-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8d523ffc6d3e22851ed25bec806a6c78d68340259e79941059752209b07a75ec", size = 469601 }, - { url = "https://files.pythonhosted.org/packages/d6/c6/843c6a33b700ef88407bd5840813e53c6986b6130d94c75c49ff7a2e31f9/optree-0.19.0-cp313-cp313t-manylinux_2_39_riscv64.whl", hash = "sha256:ca148527b6e5d59c25c733e66d4165fbcf85102f4ea10f096370fda533fe77d1", size = 436195 }, - { url = "https://files.pythonhosted.org/packages/e3/ed/13f938444de70bec2ff0edef8917a08160d41436a3cad976e541d21747f5/optree-0.19.0-cp313-cp313t-win32.whl", hash = "sha256:40d067cf87e76ad21b8ee2e6ba0347c517c88c2ce7190d666b30b4057e4de5ba", size = 343123 }, - { url = "https://files.pythonhosted.org/packages/e1/a2/5074dedbc1be5deca76fe57285ec3e7d5d475922572f92a90f3b3a4f21c5/optree-0.19.0-cp313-cp313t-win_amd64.whl", hash = "sha256:b133e1b9a30ec0bca3f875cfa68c2ce88c0b9e08b21f97f687bb669266411f4a", size = 376560 }, - { url = "https://files.pythonhosted.org/packages/49/3a/ea23a29f63d8eadab4e030ebc1329906d44f631076cd1da4751388649960/optree-0.19.0-cp313-cp313t-win_arm64.whl", hash = "sha256:45184b3c73e2147b26b139f34f15c2111cde54b8893b1104a00281c3f283b209", size = 381649 }, - { url = "https://files.pythonhosted.org/packages/81/46/643ea3d06c24d351888edfef387e611e550b64a14758169eaeb1d285e658/optree-0.19.0-cp314-cp314-android_24_arm64_v8a.whl", hash = "sha256:adf611b95d3159209c5d1eafcb2eb669733aaf75f9b6754f92d2d8b749192579", size = 921595 }, - { url = "https://files.pythonhosted.org/packages/d7/10/8717b93d93fcc3c42a6ee0e0a1a222fe25bc749b32a9e353b039dab836ce/optree-0.19.0-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:bad7bb78baa83f950bb3c59b09d7ca93d30f6bb975a1a7ce8c5f3dfe65fc834d", size = 384552 }, - { url = "https://files.pythonhosted.org/packages/a1/5e/8263600ef51ae2decb3e31776c810b8c6b5f8927697046c4434b17346d9d/optree-0.19.0-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:73f122e8acf2f1fd346e9c08f771bc1f7394359793fe632a8e1040733bdbcbec", size = 391280 }, - { url = "https://files.pythonhosted.org/packages/04/3c/40774378ebf423d7f074dfd7169f0466eb9de734f0ea5fbb368eddcb1e49/optree-0.19.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:36e426e96b3e1773e879189b12c306b58ae70052efc4087e3f14545701c7ac35", size = 421408 }, - { url = "https://files.pythonhosted.org/packages/08/67/2e19866a03a6e75eb62194a5b55e1e3154ca1517478c300232b0229f8c2a/optree-0.19.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:d22b947603be4768c2bd73a59652c94d63465f928b3099e9035f9c48dfc61953", size = 391712 }, - { url = "https://files.pythonhosted.org/packages/45/a5/7c059f643bc34c70cc5ebe63c82ae6c33b6b746219f96757d840ea1e2dcd/optree-0.19.0-cp314-cp314-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:14cc72d0c3a3c0d0b13c66801f2adc6583a01f8499fd151caaa649aabb7f99b9", size = 413471 }, - { url = "https://files.pythonhosted.org/packages/67/1a/2c5041cf476fb4b2a27f6644934ac2d079e3e4491f609cba411b3d890291/optree-0.19.0-cp314-cp314-manylinux_2_26_i686.manylinux_2_28_i686.whl", hash = "sha256:5369ac9584ef3fbb703699be694e84dbc78b730bd6d00c48c0c5a588617a1980", size = 477335 }, - { url = "https://files.pythonhosted.org/packages/40/a0/abcd7bc3218e1108d253d6783f3e610f0ac3d0e63b2720bff94eb4ed4689/optree-0.19.0-cp314-cp314-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:80b3dca5607f04316a9dcb2bb46df2f04abf4da71731bd4a53a1559c0bee6181", size = 473739 }, - { url = "https://files.pythonhosted.org/packages/82/49/7983e66210c78965bc75e386c329ec34854370d337a9ebdc4c8aede3a0b3/optree-0.19.0-cp314-cp314-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:1bb36da9b95b165c7b77fd3ff0af36a30b802cd1c020da3bcdc8aa029991c4ea", size = 475459 }, - { url = "https://files.pythonhosted.org/packages/fe/16/00261f20f467b9e8950a76ec1749f01359bf47f2fc3dac5e206de99835c0/optree-0.19.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fb220bb85128c8de71aeffb9c38be817569e4bca413b38d5e0de11ba6471ef4a", size = 456859 }, - { url = "https://files.pythonhosted.org/packages/18/31/5e78a451ba9a6ed4b0903b10080dc028e3c9b9c5797cce0ca73990fb5604/optree-0.19.0-cp314-cp314-manylinux_2_39_riscv64.whl", hash = "sha256:5d2b83a37f150f827b8b0bc2c486056f9b2203e7b0bee699d2ee96a36c090f3a", size = 418187 }, - { url = "https://files.pythonhosted.org/packages/9c/03/1516cb4fdb753cd76e5dc595217f84df48372bdabe1a7fb740a5b2530f5c/optree-0.19.0-cp314-cp314-win32.whl", hash = "sha256:b0c23d50b7f6a7c80f642307c87eee841cf513239706f2f60bd9480304170054", size = 319744 }, - { url = "https://files.pythonhosted.org/packages/7e/c3/587cc9aa8d4742cd690da79460081e7d834499e07e8b2bd2ccc4c66928df/optree-0.19.0-cp314-cp314-win_amd64.whl", hash = "sha256:ff773c852122cef6dcae68b5e252a20aaf5d2986f78e278d747e226e7829d44e", size = 345744 }, - { url = "https://files.pythonhosted.org/packages/e6/9b/c17c74ef6b85ad1a2687de8a08d1b56e3a27154b4db6c3ef1e9c2c53a96c/optree-0.19.0-cp314-cp314-win_arm64.whl", hash = "sha256:259ac2a426816d53d576c143b8dca87176af45fc8efd5dfe09db50d74a2fa0a5", size = 355307 }, - { url = "https://files.pythonhosted.org/packages/ef/4c/e881fb840cef2cead7582ee36c0e0348e66730cb2a2af1938338c72b1bf3/optree-0.19.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:428fdc8cf5dc43fa32496be6aa84fc0d8f549f899062dd9dd0aa7e3aa7f77ae9", size = 463079 }, - { url = "https://files.pythonhosted.org/packages/b6/6b/0a8538815abe28e4307dd98385d4991d36555b841b060df3295a8408b856/optree-0.19.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:d1b497032b5823a09625b118fd4df84199fb0895afb78af536d638ce7645beb6", size = 431401 }, - { url = "https://files.pythonhosted.org/packages/71/0c/d70a513fa93dbaa0e3e8c9b218b3805efb7083369cd14e1340bd2c0bc910/optree-0.19.0-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e5f05fecbca17b48451ba3455198cec9db20802c0ffbbba51eaeb421bd846a1c", size = 426111 }, - { url = "https://files.pythonhosted.org/packages/77/04/bd30c9f4e694f7b6585f333208ac7894578c1fa30dc5c938f22155df7859/optree-0.19.0-cp314-cp314t-manylinux_2_26_i686.manylinux_2_28_i686.whl", hash = "sha256:a51d0ad4e9dd089f317c94d95b7fa360e87491324e2bfa83d9c4f18dd928d4e1", size = 489872 }, - { url = "https://files.pythonhosted.org/packages/e5/17/aba83aa0e8bf31c00cdd3863c2a05854ce414426a69c094ae51210b76677/optree-0.19.0-cp314-cp314t-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:108ab83937d91658ef96c4f70a6c76b36038754f4779907ee8f127780575740f", size = 485172 }, - { url = "https://files.pythonhosted.org/packages/e0/da/52e684c42dc29d3b4d52f2029545742ef43e151cea112d9093d2ad164f53/optree-0.19.0-cp314-cp314t-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a39fdd614f46bcaf810b2bb1ed940e82b8a19e654bc325df0cc6554e25c3b7eb", size = 484506 }, - { url = "https://files.pythonhosted.org/packages/2d/f7/0d41edf484e11ba5357f91dba8d85ce06ca9d840ac7d95e58b856a49b13b/optree-0.19.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bfc1bcba22f182f39f1a80ae3ac511ebfa4daea62c3058edd021ce7a5cda3009", size = 468846 }, - { url = "https://files.pythonhosted.org/packages/79/5e/a8f49cfd6c3ae0e59dcb1155cd49f1e5ba41889c9388360264c8369589c6/optree-0.19.0-cp314-cp314t-manylinux_2_39_riscv64.whl", hash = "sha256:afe595a052cc45d3addb6045f04a3ca7e1fb664de032ecbbb2bfd76dfe1fcb61", size = 433899 }, - { url = "https://files.pythonhosted.org/packages/9a/1b/4105e562d86b2de7eb3f240164a7dd3948e268878a9ee8925bfe1ad1da4f/optree-0.19.0-cp314-cp314t-win32.whl", hash = "sha256:b15ab972e2133e70570259386684624a17128daab7fb353a0a7435e9dd2c7354", size = 351719 }, - { url = "https://files.pythonhosted.org/packages/c4/43/bbc4c7a1f37f1a0ed6efe07a5c44b2835e81d1f6ce1cca6a395a2339e60f/optree-0.19.0-cp314-cp314t-win_amd64.whl", hash = "sha256:c90c15a80c325c2c6e03e20c95350df5db4591d35e8e4a35a40d2f865c260193", size = 391937 }, - { url = "https://files.pythonhosted.org/packages/62/12/6758b43dbddc6911e3225a15ca686c913959fb63c267840b54f0002be503/optree-0.19.0-cp314-cp314t-win_arm64.whl", hash = "sha256:a1e7b358df8fc4b97a05380d446e87b08eac899c1f34d9846b9afa0be7f96bc7", size = 389259 }, - { url = "https://files.pythonhosted.org/packages/83/88/a31c2b7ee12f2c7a24a8d57aeecbbcd4c513152212aa1958fff736900033/optree-0.19.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:2806e7baafc3c2985b71a5202fe3bde75838c7f09c0df0aa43a2e7ae12e65587", size = 411712 }, - { url = "https://files.pythonhosted.org/packages/e7/db/33b61930bf2a879d9b114e63723ccf3965404d4fb5caffad5bee1a5be61c/optree-0.19.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:c39df2d664e3d54faaabfeda3ccfc4a768fc1314578b9ef741055387e13ef3bb", size = 384082 }, - { url = "https://files.pythonhosted.org/packages/81/b1/64d04f0169feba3555434013ba2328019c2bde4d5d14600acfdcb91102a9/optree-0.19.0-pp311-pypy311_pp73-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8bdb37b57863f18fad5601af4b94e8bc0ac145040b0ecbbc87f93d27fd6a676e", size = 403543 }, - { url = "https://files.pythonhosted.org/packages/39/5c/cc9683a8c9eb54ba48739b7c394503d7df7a22b9f4bd2463ace46bbec593/optree-0.19.0-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6b279f6d5e1cfe681b8bc8678529048081fe5ddaf49333a8bb13f3fb978dfdea", size = 444272 }, - { url = "https://files.pythonhosted.org/packages/10/a7/9eb8b63ea378274517233ba0f2f0091a4cf38d92f91c9b1e6e646af31a8b/optree-0.19.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:7bcb4e3e0b2b6db57e0882f0ba6b03a07d1489887e7e75d75c42b0e2be9f0019", size = 335393 }, +sdist = { url = "https://files.pythonhosted.org/packages/44/63/92328a17ab7836562fe0129e605f685a88db35ce98427c34ff48ee4ec157/optree-0.19.1.tar.gz", hash = "sha256:4497d1c9197b8c6842e511368163d318ce536521ebdcff8bebb7551dcdfac532", size = 177531, upload-time = "2026-05-06T02:32:39.704Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c3/f2/4671a78193f96e86c1343fe04324091e163973d0058b292c10bc3387bb70/optree-0.19.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a496b864fe1fe0b5ea23d1ee3d1ef958d910704661808db2b2d2e16a0cfac96c", size = 414314, upload-time = "2026-05-06T02:30:43.812Z" }, + { url = "https://files.pythonhosted.org/packages/d6/93/7decea24656f416d61fa57b7113b1fbdbc042b7ab421399a84e1755676a1/optree-0.19.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c1667e502e0eda9477925fb17c2ad879b199a2283ac98f18e6453692819b7811", size = 385006, upload-time = "2026-05-06T02:30:44.895Z" }, + { url = "https://files.pythonhosted.org/packages/af/2e/9d1bd2527481681c4399beeeabba11dca36b16ec814579f2e8cc6bc2af96/optree-0.19.1-cp311-cp311-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:42e367a9d81e57c31a23247094727987a2f64b708901233a42a24d44d24e93f6", size = 406124, upload-time = "2026-05-06T02:30:46.13Z" }, + { url = "https://files.pythonhosted.org/packages/df/29/cdb40de6307809fa8e9452e4f9a65881a3140d01d9d589a07e9d054d8e1c/optree-0.19.1-cp311-cp311-manylinux_2_26_i686.manylinux_2_28_i686.whl", hash = "sha256:96fad6c7b3a6fde3a0c8655fd003359cd247f8400749217502591a5ffc328699", size = 466772, upload-time = "2026-05-06T02:30:47.766Z" }, + { url = "https://files.pythonhosted.org/packages/cb/15/4645e1816e815a1306bbb7e3e2e6ba124f6dc325f8088a2db69301219a0c/optree-0.19.1-cp311-cp311-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f3a900df0ffb9b8259961b337289754531a7e0a5de2f681e9c80866b6a7cb74e", size = 466203, upload-time = "2026-05-06T02:30:49.04Z" }, + { url = "https://files.pythonhosted.org/packages/e6/d2/5758c76bdd7034b721d84c7f0fd911f3b39dcb489eeb27f674aaae8a5f5c/optree-0.19.1-cp311-cp311-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:27c8dc0f89ade9233aa7ed25ce15991da188e6950eb17cc0c313fc1f327c5b0b", size = 465030, upload-time = "2026-05-06T02:30:50.254Z" }, + { url = "https://files.pythonhosted.org/packages/09/b9/f668bc51129c0fec7728ae8b43180417fe1c1fe99f71d302739f6cc50944/optree-0.19.1-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:38f2e503fad50aff58cade85db448002d4adc72f4b3b50dcc7f3ef4bcd3b0173", size = 447141, upload-time = "2026-05-06T02:30:51.42Z" }, + { url = "https://files.pythonhosted.org/packages/a4/08/a7b8862e4465bf250c3ccc78db4d10b9a2cf90ce4db3681cbdf7eb076fb7/optree-0.19.1-cp311-cp311-manylinux_2_39_riscv64.whl", hash = "sha256:5dc35cb31540ab6ed9850b0f8865ccd400994ebd51fcf0c156cc772073f43c04", size = 410016, upload-time = "2026-05-06T02:30:52.695Z" }, + { url = "https://files.pythonhosted.org/packages/fb/04/04b71a34cf5e663a1df029acceb5efc8a96c8dc4b0b6af6e98486638e913/optree-0.19.1-cp311-cp311-win32.whl", hash = "sha256:d32b1261be71211f77837e839e43a3e3e8fc57707091d2454d0a88590fb6abe8", size = 311810, upload-time = "2026-05-06T02:30:53.879Z" }, + { url = "https://files.pythonhosted.org/packages/22/64/3cc7b08cb1c0f1949895f9490217ca8db6ced7f3bf75c65a5bf31c07bf1e/optree-0.19.1-cp311-cp311-win_amd64.whl", hash = "sha256:cd28a527bb363a1d7d28e8b2fb62816ace6743418bb86e9c5f27ea6877dcdf6c", size = 337620, upload-time = "2026-05-06T02:30:55.262Z" }, + { url = "https://files.pythonhosted.org/packages/6c/14/85f4b05765287658529f09ede10461224161dcf0e29e6fce1ae488451cfe/optree-0.19.1-cp311-cp311-win_arm64.whl", hash = "sha256:7853b58aa084e882ea078f390936bd92e46972eb8f9b5e654360b6480ca7283b", size = 349337, upload-time = "2026-05-06T02:30:56.647Z" }, + { url = "https://files.pythonhosted.org/packages/ba/a7/cb5567029a608a296b0ca224025d03bba0365b41df19085b9b580191f6f2/optree-0.19.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:96e5c7c3b9144f08ae40c3d9848cfbcfa36b6bead0f8215ad071d5922ee6c4a5", size = 424023, upload-time = "2026-05-06T02:30:57.732Z" }, + { url = "https://files.pythonhosted.org/packages/b9/a1/3651fb32fa8617108204aa4056d283af742020e0987d106f41402005d800/optree-0.19.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5d9d198343e1e6ced18bef0cbff84091c1877964fc4a121df33f18840e073a01", size = 394782, upload-time = "2026-05-06T02:30:59.239Z" }, + { url = "https://files.pythonhosted.org/packages/c2/1e/676470909aa64d7aba7c5edf83b171dc83b7af901d9ebb8e6d7512fe913a/optree-0.19.1-cp312-cp312-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7a1202371d9fe3aa75f3e886b1f871aac4991a655aadb65e54f58a3ae9388ab2", size = 413157, upload-time = "2026-05-06T02:31:00.339Z" }, + { url = "https://files.pythonhosted.org/packages/f4/41/1a4c58f2af5742b9d9e21ea9e45c6c3c49463b5e2a0537e84ead1e9597ca/optree-0.19.1-cp312-cp312-manylinux_2_26_i686.manylinux_2_28_i686.whl", hash = "sha256:d41ccc4c20bfeae01d1d221c057a6d026e84e32229664952eddcdbe4b9b71417", size = 476923, upload-time = "2026-05-06T02:31:01.492Z" }, + { url = "https://files.pythonhosted.org/packages/10/c1/f62167bd9d6f6c948b191a0943923404678d47100f777f4a8fb37816e6f8/optree-0.19.1-cp312-cp312-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7d934f240b109c6891dd06b2e30400b123b8a4b6ed31dcd0db2ae2378d30a6e8", size = 475385, upload-time = "2026-05-06T02:31:02.836Z" }, + { url = "https://files.pythonhosted.org/packages/30/5e/5323c5fa3024fdd900bdd8f14621139ed844c2247bf1a26e7cf5c1116188/optree-0.19.1-cp312-cp312-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ddeefb7ca799c09647e332ebc1a5f6c09888a5a0e51f2dff4ca55e65b42a8c14", size = 474406, upload-time = "2026-05-06T02:31:04.023Z" }, + { url = "https://files.pythonhosted.org/packages/e2/6a/54e4c47e61a51504a5224c933722e0c8a69925aacec4c08175e9675aeb81/optree-0.19.1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f0ce49f64f804f7f35f2f9c2a21e3ba94c090199fccdcfd40e3ded4426c5c175", size = 457596, upload-time = "2026-05-06T02:31:05.695Z" }, + { url = "https://files.pythonhosted.org/packages/a7/12/bba07c0b769586c6bd54e81f1f734cad103dbe30abbadee940fe7d3e330e/optree-0.19.1-cp312-cp312-manylinux_2_39_riscv64.whl", hash = "sha256:e0f02600832ab8d0f6c934dcb5c339e17a36938d477641a45798e02625ebe107", size = 417900, upload-time = "2026-05-06T02:31:07.251Z" }, + { url = "https://files.pythonhosted.org/packages/9f/8f/6ae994bb47f9394b33912a14593f9247737dd6c3303811550e5a3e918107/optree-0.19.1-cp312-cp312-win32.whl", hash = "sha256:f10d58c1a17e1b32f9d9b5e1b9d1ad964d99c1113d9df0b9f62f2fe7dde19909", size = 317302, upload-time = "2026-05-06T02:31:08.627Z" }, + { url = "https://files.pythonhosted.org/packages/31/97/d7e3ec79dcdde81f785a0446acf75fea77723f5ca4b98556350d7877986f/optree-0.19.1-cp312-cp312-win_amd64.whl", hash = "sha256:06f5c8a4cf356a1a276ce5cec1be44719ed260690f79c036d04b4d427e801258", size = 341362, upload-time = "2026-05-06T02:31:09.689Z" }, + { url = "https://files.pythonhosted.org/packages/33/97/813afb84a81fd8ae65444730907c05f0775fd6c79d3359c9e84bd3370445/optree-0.19.1-cp312-cp312-win_arm64.whl", hash = "sha256:a33bd23fc5c67ecb9ff491b75fde10cd9b53f47f8a876de842090e8c7a2437e1", size = 351838, upload-time = "2026-05-06T02:31:11.086Z" }, + { url = "https://files.pythonhosted.org/packages/c2/7b/0f2f3c9d55dda5127624daf68ff802ab624b739dd4b32aef505dac0c8e02/optree-0.19.1-cp313-cp313-android_24_arm64_v8a.whl", hash = "sha256:f144cfd65fb17c6aa2c51818614eb009e6052d3d6ace91f7e570b1318cdcac4c", size = 929090, upload-time = "2026-05-06T02:31:12.267Z" }, + { url = "https://files.pythonhosted.org/packages/15/e2/670d260dfd0532d64272dd6f7edd540a09d7040c0342b6cc6cf773568ea4/optree-0.19.1-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:39a006735d2a0a68751a3bc33d670184fddcd86db63b0293e1e819739e8105e4", size = 391528, upload-time = "2026-05-06T02:31:14.212Z" }, + { url = "https://files.pythonhosted.org/packages/f4/96/46c15e80b0c97e2ba6aba11339008a37cabc5ccf55c31c6c60aecdb79638/optree-0.19.1-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:d2cb43c36638f469f5d8f4cf638e914de90c62242d8bed29f1b4487e0346ab94", size = 398231, upload-time = "2026-05-06T02:31:15.519Z" }, + { url = "https://files.pythonhosted.org/packages/7e/39/9d7d22cdaeb9a40ace2485f91c5b7c5f3a7f688575e2621e436561211cc1/optree-0.19.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e70faa00ab69331f49f8337d45021bed09ae2265d1db72eea9d7817af2b73c64", size = 429852, upload-time = "2026-05-06T02:31:16.992Z" }, + { url = "https://files.pythonhosted.org/packages/79/4c/1da9e8375e7b7fd9671dc5987682b042f6412c4d6fd9da03296403818d9f/optree-0.19.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1c5d21176b670407f4555aae40711668832599c4fb0627000c5ce3ed0d6e2dae", size = 398688, upload-time = "2026-05-06T02:31:18.113Z" }, + { url = "https://files.pythonhosted.org/packages/d3/50/cd2d178099618093f5a9fd1c9de80af2b428879922eae1e9f27f1002c8be/optree-0.19.1-cp313-cp313-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f658fa46305b2bdccdc5bb2cb07818aeaef88a1085499deda5be48a0a58d2971", size = 417560, upload-time = "2026-05-06T02:31:19.391Z" }, + { url = "https://files.pythonhosted.org/packages/d7/b0/f22ff5632083b5032caa80208dd202f8e963ed4aac11afa0a0f6a307fd68/optree-0.19.1-cp313-cp313-manylinux_2_26_i686.manylinux_2_28_i686.whl", hash = "sha256:e757079d44a00319447f43df5c51e55bf9b62d9f05eea0e2db5ff7c7ca5ec71d", size = 482937, upload-time = "2026-05-06T02:31:20.799Z" }, + { url = "https://files.pythonhosted.org/packages/7d/d4/7499d28be8b11eb40668262d27802119fe7e6ec4cd8816b76a1acd7b08f5/optree-0.19.1-cp313-cp313-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:9690c132822d9dee479cf7dff8cc52a67c8af42a4f7529d21f0f4f1d99e4c84e", size = 477864, upload-time = "2026-05-06T02:31:22.077Z" }, + { url = "https://files.pythonhosted.org/packages/b1/6e/6c6fa6f1159ac68f4ee7666610127fb4c14d47a2fa7a0a48de3aecc24d4b/optree-0.19.1-cp313-cp313-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:544b70958dbd7e732bc6874e0180c609c9052115937d0ec28123bb49c1a574aa", size = 478319, upload-time = "2026-05-06T02:31:23.266Z" }, + { url = "https://files.pythonhosted.org/packages/68/b5/8a2427bbe4ee59e2ce26a14125728e3b48c7030c80984ba07d0e5d804d37/optree-0.19.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9dde5b756946c1f1458aeab248a7a9b0c01bb06b5787de9f06d52ad38b745557", size = 462379, upload-time = "2026-05-06T02:31:24.543Z" }, + { url = "https://files.pythonhosted.org/packages/ee/0c/a073eeaea4d4f68e02d5883ed8268746a296e6749e3c46e0124ca45f306c/optree-0.19.1-cp313-cp313-manylinux_2_39_riscv64.whl", hash = "sha256:f1d7838e8b1b62258abd73a5911afad1153ed76822070558c3ba7e0bb5b44192", size = 423061, upload-time = "2026-05-06T02:31:25.652Z" }, + { url = "https://files.pythonhosted.org/packages/5f/34/637b151d071ca94aea0087322f470ce84c5828ef6b9c0de7dc7b4420a1cf/optree-0.19.1-cp313-cp313-win32.whl", hash = "sha256:9870d33ec50cca0c46c2b431cea24c6247457da15fd4ad66ccb8ab78145c1490", size = 317439, upload-time = "2026-05-06T02:31:27.304Z" }, + { url = "https://files.pythonhosted.org/packages/50/52/49b8a8d9e94c57c6fa5008953f84a1c36a4119a3b90dcb7df745f1f05a00/optree-0.19.1-cp313-cp313-win_amd64.whl", hash = "sha256:aa0845b725bcd0029e179cf9b4bc2cc016c7358e56fc7c0d2c43bf4d514c96cf", size = 343906, upload-time = "2026-05-06T02:31:28.774Z" }, + { url = "https://files.pythonhosted.org/packages/c6/a9/1ae0a9685f5301f454f01d2490065b98df6956f90b1b2fd1cea9daa6d820/optree-0.19.1-cp313-cp313-win_arm64.whl", hash = "sha256:6f0b1efc177bed6495f78d39d5aa495ccb31cc20bcf64bb1b806ca4c919f4049", size = 353146, upload-time = "2026-05-06T02:31:29.976Z" }, + { url = "https://files.pythonhosted.org/packages/9c/77/4c8108cbce2c8ae2aa4b6adc7874082882e32cf131cb64b3a4411f50dec4/optree-0.19.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:b964bcdb5cfe367cdf56447e80ba5a49123098d8c4e8e68b41c20890eec6e58e", size = 469723, upload-time = "2026-05-06T02:31:31.425Z" }, + { url = "https://files.pythonhosted.org/packages/64/33/ce9b54646ed4ab5773a9dc59767dadfe3de8bb2e97a3ed19205b995a7a31/optree-0.19.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:08ccec0ee5a565eb5aa4fe30383016a358627ea23d968ec8ab28b1f2ce4ce3d8", size = 437071, upload-time = "2026-05-06T02:31:33.027Z" }, + { url = "https://files.pythonhosted.org/packages/79/55/04260128a726e3550b49467a65bff859452897144b68bae54b2f2e5c27f1/optree-0.19.1-cp313-cp313t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:672588408906051d3e9a99aca6c0af93c6e0b638137a701418088eaa0bb6c719", size = 433503, upload-time = "2026-05-06T02:31:34.423Z" }, + { url = "https://files.pythonhosted.org/packages/d6/99/6a4cc29389667efa089a0c476b7c36b7d0a66e10dd2d8c2d19c776977566/optree-0.19.1-cp313-cp313t-manylinux_2_26_i686.manylinux_2_28_i686.whl", hash = "sha256:d16cef4d0555d49ce221d80249f1285a2d3faf932e451c3ce6cb8ccb6a846767", size = 496305, upload-time = "2026-05-06T02:31:35.835Z" }, + { url = "https://files.pythonhosted.org/packages/7f/46/506aa1a64abce69e2f4cec9cdac3da0cae207cf04c5e70e7f143bf8b29d8/optree-0.19.1-cp313-cp313t-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:dc2db0b449baff53aa7e583306101de0ade5e5ae9e6fce78400eb2319bbd23dc", size = 492759, upload-time = "2026-05-06T02:31:37.265Z" }, + { url = "https://files.pythonhosted.org/packages/f5/28/2210de9a68722007fe007da3cae1a5971b92fc8113b5eecef66a04637959/optree-0.19.1-cp313-cp313t-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:76b3e9e5d37e6b05ec82fff91758c8c0e27e159b35faea4b33d5eb975d720257", size = 495447, upload-time = "2026-05-06T02:31:38.505Z" }, + { url = "https://files.pythonhosted.org/packages/d9/61/40c3463e52914d552c66c760ae15e673137c4cc1d1d9f8da0d745656193a/optree-0.19.1-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:03faa8e23fdaf3a18f9a1568c2c0eb0641a6aa05baf3a20639bd11fb34664700", size = 475564, upload-time = "2026-05-06T02:31:39.732Z" }, + { url = "https://files.pythonhosted.org/packages/0a/66/1603680fa924e68e5697c1229510c0645db0a9c633a12d1a9bfdbfc9cb74/optree-0.19.1-cp313-cp313t-manylinux_2_39_riscv64.whl", hash = "sha256:a9b9c7e9148ec470124dc4c1d1cd1485dbeb35973357b5911b181a79090426d2", size = 442414, upload-time = "2026-05-06T02:31:40.908Z" }, + { url = "https://files.pythonhosted.org/packages/a5/58/34820bab11f28ba6b03fe9e151880ad591b43f26648f058c94451fbdfc3a/optree-0.19.1-cp313-cp313t-win32.whl", hash = "sha256:ab8ad9803376d553a2958471b6bb6842b7e15888e19cc6aeb76da96c6afd948d", size = 348644, upload-time = "2026-05-06T02:31:42.038Z" }, + { url = "https://files.pythonhosted.org/packages/d9/2b/0be3f8b9765f366e3e12d0590e9c6514de110d0c5b3b9002f49e56bf15b1/optree-0.19.1-cp313-cp313t-win_amd64.whl", hash = "sha256:afd4abeb2783b2367093287bc6268ac9af244b20c8d9b01696ccfe817483b66c", size = 382445, upload-time = "2026-05-06T02:31:43.166Z" }, + { url = "https://files.pythonhosted.org/packages/fc/fa/8c0882cdd42e28a23c1998297c8ad1202194510cbba8b050251429c641c0/optree-0.19.1-cp313-cp313t-win_arm64.whl", hash = "sha256:b9120510d3f951e268e417a3f64f335bc1c539e1e80bff2129ddc6fb60ac7b56", size = 388040, upload-time = "2026-05-06T02:31:44.661Z" }, + { url = "https://files.pythonhosted.org/packages/e3/da/4e16e26375c56c9e40760697af4e2b72f196c2099e96cc783b63dcc862a8/optree-0.19.1-cp314-cp314-android_24_arm64_v8a.whl", hash = "sha256:e1951ddc870f67430310fd17393971c30510ee9fd290525b44c12afe25f3c307", size = 927808, upload-time = "2026-05-06T02:31:45.954Z" }, + { url = "https://files.pythonhosted.org/packages/6b/87/ff1c6bb6b79a5d0b70b83f7ae8b78811a406a749b3ae4478a2122a7afb66/optree-0.19.1-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:ae9d42718ebf985cdad3182364b5cf829193b8fd2c6d993fbb4111d38e2bdf96", size = 390981, upload-time = "2026-05-06T02:31:47.38Z" }, + { url = "https://files.pythonhosted.org/packages/82/25/fc648710102960f87d18cd8fc8a24afe14a5ec7827c64dfb1340230c0794/optree-0.19.1-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:930268ebfdebca43a8808f6293910d6ade2fe7c84fa784692017d7120d285226", size = 397756, upload-time = "2026-05-06T02:31:48.76Z" }, + { url = "https://files.pythonhosted.org/packages/24/f6/a7bf5d75a6481038bbb61846d87d43124d63741385796ef7b37d326f46bd/optree-0.19.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:b2757c5d922aab76cfc9b870c373fb35209c2094e3c912733b326c043e85a0c6", size = 427424, upload-time = "2026-05-06T02:31:49.838Z" }, + { url = "https://files.pythonhosted.org/packages/49/cc/14dd93887295859457e507fc46a847b68ae8f20c42b2fde4d8a749c94bbc/optree-0.19.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:b17a7b70ff8bd406c2142914c5ab0a57f8bcfb9f52181f7012e32406bbdbfdda", size = 398242, upload-time = "2026-05-06T02:31:51.262Z" }, + { url = "https://files.pythonhosted.org/packages/17/b5/ac51aa118dd918761519fbc031865b1d6f850453e9a7ac0c3da21109c4f0/optree-0.19.1-cp314-cp314-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:987bba55366917d9829f45b5ee86499ecc87a30e9103072db9ab8d67f9958179", size = 419568, upload-time = "2026-05-06T02:31:52.349Z" }, + { url = "https://files.pythonhosted.org/packages/ec/41/25144e61f76278b9e0a5d4189c7083fe853164c5f7328a1f5aac43d964c2/optree-0.19.1-cp314-cp314-manylinux_2_26_i686.manylinux_2_28_i686.whl", hash = "sha256:d3bba2af7a5fce0c25e99024688e68dfe9be41e3d6e92720febbefdc879fba38", size = 482797, upload-time = "2026-05-06T02:31:53.471Z" }, + { url = "https://files.pythonhosted.org/packages/22/47/2c76c7ce937323988770c41126e0e380bcb73a816f68a767f23b5c33aced/optree-0.19.1-cp314-cp314-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:dae6c247cc8751bd2f167951468769f5c98f8cfdae31c0db0f2eb4145a6ec560", size = 479794, upload-time = "2026-05-06T02:31:54.843Z" }, + { url = "https://files.pythonhosted.org/packages/7c/ca/bd9553f94bec0bc7860f10ae177c14ca265ab19ddb463122be22fa335ee8/optree-0.19.1-cp314-cp314-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:17a986fd91ccdc18bb7b587ca1f508c1761580a93517e6db33a13b22e46acb9b", size = 481084, upload-time = "2026-05-06T02:31:56.261Z" }, + { url = "https://files.pythonhosted.org/packages/9c/1a/4834b1f2fb1847412353d7342eb7a1d001a4f3bd9d24155e057135a4aa44/optree-0.19.1-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3d0e1493429ae1d1a5e34855774ee604c974a8f76656bd0e562cdbf9466c9b1f", size = 462955, upload-time = "2026-05-06T02:31:57.829Z" }, + { url = "https://files.pythonhosted.org/packages/f4/88/598fb91c06fee3d8b08568779b011225dc2b66140927bd0b2b2d9b40a566/optree-0.19.1-cp314-cp314-manylinux_2_39_riscv64.whl", hash = "sha256:f61a01ed9991193ed6f3db8e956ede05218190a32ca2ddfb71cfc40c8daba1d5", size = 423754, upload-time = "2026-05-06T02:31:59.291Z" }, + { url = "https://files.pythonhosted.org/packages/20/8a/83c64ecadc686e08310fc9c20bc0bbe6453e89b69257e08887818dac7886/optree-0.19.1-cp314-cp314-win32.whl", hash = "sha256:b0c920579bddc3b18a0e051850f017618e24efcc19ba83dcd415cf74db5fd904", size = 325214, upload-time = "2026-05-06T02:32:00.802Z" }, + { url = "https://files.pythonhosted.org/packages/96/c3/4f2f318b98465376bbb7a06a33da553c688b3ed39dafbb8307f824eef74a/optree-0.19.1-cp314-cp314-win_amd64.whl", hash = "sha256:50d77b91a8cd01adf422472b7edf39fc445b0268816176a868a385d28f8367c2", size = 351654, upload-time = "2026-05-06T02:32:01.944Z" }, + { url = "https://files.pythonhosted.org/packages/1c/ab/55d7508e87055c730fe7207cfd0c45183a07ddf1f91d9e73d017a7f8c1f4/optree-0.19.1-cp314-cp314-win_arm64.whl", hash = "sha256:c682ab6711b7a623503711fa661a2bba7886e1c21dc06c3b7febba101b458051", size = 361610, upload-time = "2026-05-06T02:32:03.003Z" }, + { url = "https://files.pythonhosted.org/packages/ae/2d/4f7facd482d56079b7adb8ce3fede19f41629bc0463e8ee25907f1dba36c/optree-0.19.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:068edb89fadd94f6f57fdb51f4ad2c764b5a0bfd00903c55ffe433c2863a8037", size = 469130, upload-time = "2026-05-06T02:32:04.395Z" }, + { url = "https://files.pythonhosted.org/packages/92/60/f7539012aa8a7488c1e34f66b76eadc384c3152dd9800973f1b5fe045dfd/optree-0.19.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:a609c90e4f64e4f3e2b5b3cc022210314834737e0e61a745485e33b33eae773b", size = 437286, upload-time = "2026-05-06T02:32:05.527Z" }, + { url = "https://files.pythonhosted.org/packages/9b/3f/a5f8fb3ec3840f885de52d7a793ba57ace17990e3a9b3797218425ffe842/optree-0.19.1-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:dfae64c4c371640a4b3e2a9e3e6aa3a3e8cdf2da5247a88fef5b632614b948a6", size = 431954, upload-time = "2026-05-06T02:32:06.83Z" }, + { url = "https://files.pythonhosted.org/packages/68/dc/6d0ef14bc82bd54046c1a066d25fa6854123a6b29fd691f1f95dec3ab45f/optree-0.19.1-cp314-cp314t-manylinux_2_26_i686.manylinux_2_28_i686.whl", hash = "sha256:470742544ff2d4b63843023f38dcfb83e82c3a9877c783dee0e69cbb974de6d1", size = 494631, upload-time = "2026-05-06T02:32:08.038Z" }, + { url = "https://files.pythonhosted.org/packages/b8/9a/9e183c610c414cba581f9afda7610589d89cae229d627b14f8480425d975/optree-0.19.1-cp314-cp314t-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:1a74e0656ccef45b1fec07b9d964ce97f3def8bab73711f56175076c4259884f", size = 491786, upload-time = "2026-05-06T02:32:09.363Z" }, + { url = "https://files.pythonhosted.org/packages/4d/73/266b9de8eb5b16bfe7010c90c55840517d5d61ee6e0ca64901440296d97a/optree-0.19.1-cp314-cp314t-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f55841132ba8a34dbbd85e0c2cf990602384eea0e4638df986cd3266482f4a17", size = 490876, upload-time = "2026-05-06T02:32:11.388Z" }, + { url = "https://files.pythonhosted.org/packages/b3/8d/42a8ca6277ef93d47ab0986e30a25134206afe0c6e6c3425c8736b2677ba/optree-0.19.1-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a5f8383952f18d5a4ec6b248d8ae6fe27012434ad9750aa33a821ad4846da5af", size = 475079, upload-time = "2026-05-06T02:32:12.768Z" }, + { url = "https://files.pythonhosted.org/packages/63/91/e363f4adda292f891ca0cf5748010fea955737bdf494cc11d4c3bcda6935/optree-0.19.1-cp314-cp314t-manylinux_2_39_riscv64.whl", hash = "sha256:de8acbed5965beae6f6b0456fcb8d1afaea1fe300810739e88645e22138849bc", size = 440119, upload-time = "2026-05-06T02:32:14.096Z" }, + { url = "https://files.pythonhosted.org/packages/3f/eb/489d22ef3cadb2f5f3bbd6e6099d17b5a521ff533e086f78f005c3358017/optree-0.19.1-cp314-cp314t-win32.whl", hash = "sha256:312048e69dc88de26915674f961bf38980a765a6b48ead2f1672858a39402c41", size = 357465, upload-time = "2026-05-06T02:32:15.424Z" }, + { url = "https://files.pythonhosted.org/packages/e0/34/7f48b7034ff75d2eb3e94e2196709ddbf762798fb621f9508899fa66b44e/optree-0.19.1-cp314-cp314t-win_amd64.whl", hash = "sha256:60e9345405d7b06cafdf1b1dd2e2261ceddddce10f35729240f90e2bab845a0b", size = 397783, upload-time = "2026-05-06T02:32:16.853Z" }, + { url = "https://files.pythonhosted.org/packages/07/42/6d6f93416c66820cb8753e65b5ff43c47480af9c4911bd2b8406ff0f7f27/optree-0.19.1-cp314-cp314t-win_arm64.whl", hash = "sha256:4e103e212d1e8fe0399ed076eff80a905fb14929729bbd994d3660110a27a252", size = 396064, upload-time = "2026-05-06T02:32:18.077Z" }, + { url = "https://files.pythonhosted.org/packages/2b/d4/ffeedc86f8b91e5c17994f38bd1f7aa2e20f9b70a6d3ae906af16414626c/optree-0.19.1-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:3f4f1c276fa06227cdaf58349d22a3231b3dd3d47de1f90a86222ebf831fc397", size = 417543, upload-time = "2026-05-06T02:32:32.592Z" }, + { url = "https://files.pythonhosted.org/packages/52/0b/80fb1b289940e34858cb89f05bc7ce23d6d1272886c2f78bc7e3ab1a306b/optree-0.19.1-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:77d93eafbd0046c7350bc592ab8e3814abbd39a6d716b5b1e5d652cc486f445c", size = 390184, upload-time = "2026-05-06T02:32:34.273Z" }, + { url = "https://files.pythonhosted.org/packages/fc/67/f31784a7a2dcc0c1f84b691afc552ea5b26db5f56657692a12954a828db4/optree-0.19.1-pp311-pypy311_pp73-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3507ae5db5827eef3da42d04c5a41df649cedc2e42d5d39dc0f869d36915a00b", size = 409025, upload-time = "2026-05-06T02:32:35.817Z" }, + { url = "https://files.pythonhosted.org/packages/3e/a5/647b93eb16244cc7f6dfccc025ac495245e306ff4cb8f9ad15718219141a/optree-0.19.1-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:732c4581fb666869b8b391ec4ca13d2729795f9abe72b5aec2e582bcbea1975d", size = 449514, upload-time = "2026-05-06T02:32:37.014Z" }, + { url = "https://files.pythonhosted.org/packages/c7/8e/d251c9338771ef0f9db8e538bd77810100c495734b57494464c7e223f0d0/optree-0.19.1-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:e12ee3776a16f6feaa8263b92469ad546b870af71d50602745855d8449219221", size = 341586, upload-time = "2026-05-06T02:32:38.308Z" }, ] [[package]] name = "packaging" -version = "26.1" +version = "26.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/df/de/0d2b39fb4af88a0258f3bac87dfcbb48e73fbdea4a2ed0e2213f9a4c2f9a/packaging-26.1.tar.gz", hash = "sha256:f042152b681c4bfac5cae2742a55e103d27ab2ec0f3d88037136b6bfe7c9c5de", size = 215519 } +sdist = { url = "https://files.pythonhosted.org/packages/d7/f1/e7a6dd94a8d4a5626c03e4e99c87f241ba9e350cd9e6d75123f992427270/packaging-26.2.tar.gz", hash = "sha256:ff452ff5a3e828ce110190feff1178bb1f2ea2281fa2075aadb987c2fb221661", size = 228134, upload-time = "2026-04-24T20:15:23.917Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/7a/c2/920ef838e2f0028c8262f16101ec09ebd5969864e5a64c4c05fad0617c56/packaging-26.1-py3-none-any.whl", hash = "sha256:5d9c0669c6285e491e0ced2eee587eaf67b670d94a19e94e3984a481aba6802f", size = 95831 }, + { url = "https://files.pythonhosted.org/packages/df/b2/87e62e8c3e2f4b32e5fe99e0b86d576da1312593b39f47d8ceef365e95ed/packaging-26.2-py3-none-any.whl", hash = "sha256:5fc45236b9446107ff2415ce77c807cee2862cb6fac22b8a73826d0693b0980e", size = 100195, upload-time = "2026-04-24T20:15:22.081Z" }, ] [[package]] name = "pandas" -version = "3.0.2" +version = "3.0.5" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "numpy" }, + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, + { name = "numpy", version = "2.5.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, { name = "python-dateutil" }, { name = "tzdata", marker = "sys_platform == 'emscripten' or sys_platform == 'win32'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/da/99/b342345300f13440fe9fe385c3c481e2d9a595ee3bab4d3219247ac94e9a/pandas-3.0.2.tar.gz", hash = "sha256:f4753e73e34c8d83221ba58f232433fca2748be8b18dbca02d242ed153945043", size = 4645855 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/97/35/6411db530c618e0e0005187e35aa02ce60ae4c4c4d206964a2f978217c27/pandas-3.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a727a73cbdba2f7458dc82449e2315899d5140b449015d822f515749a46cbbe0", size = 10326926 }, - { url = "https://files.pythonhosted.org/packages/c4/d3/b7da1d5d7dbdc5ef52ed7debd2b484313b832982266905315dad5a0bf0b1/pandas-3.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:dbbd4aa20ca51e63b53bbde6a0fa4254b1aaabb74d2f542df7a7959feb1d760c", size = 9926987 }, - { url = "https://files.pythonhosted.org/packages/52/77/9b1c2d6070b5dbe239a7bc889e21bfa58720793fb902d1e070695d87c6d0/pandas-3.0.2-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:339dda302bd8369dedeae979cb750e484d549b563c3f54f3922cb8ff4978c5eb", size = 10757067 }, - { url = "https://files.pythonhosted.org/packages/20/17/ec40d981705654853726e7ac9aea9ddbb4a5d9cf54d8472222f4f3de06c2/pandas-3.0.2-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:61c2fd96d72b983a9891b2598f286befd4ad262161a609c92dc1652544b46b76", size = 11258787 }, - { url = "https://files.pythonhosted.org/packages/90/e3/3f1126d43d3702ca8773871a81c9f15122a1f412342cc56284ffda5b1f70/pandas-3.0.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c934008c733b8bbea273ea308b73b3156f0181e5b72960790b09c18a2794fe1e", size = 11771616 }, - { url = "https://files.pythonhosted.org/packages/2e/cf/0f4e268e1f5062e44a6bda9f925806721cd4c95c2b808a4c82ebe914f96b/pandas-3.0.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:60a80bb4feacbef5e1447a3f82c33209c8b7e07f28d805cfd1fb951e5cb443aa", size = 12337623 }, - { url = "https://files.pythonhosted.org/packages/44/a0/97a6339859d4acb2536efb24feb6708e82f7d33b2ed7e036f2983fcced82/pandas-3.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:ed72cb3f45190874eb579c64fa92d9df74e98fd63e2be7f62bce5ace0ade61df", size = 9897372 }, - { url = "https://files.pythonhosted.org/packages/8f/eb/781516b808a99ddf288143cec46b342b3016c3414d137da1fdc3290d8860/pandas-3.0.2-cp311-cp311-win_arm64.whl", hash = "sha256:f12b1a9e332c01e09510586f8ca9b108fd631fd656af82e452d7315ef6df5f9f", size = 9154922 }, - { url = "https://files.pythonhosted.org/packages/f3/b0/c20bd4d6d3f736e6bd6b55794e9cd0a617b858eaad27c8f410ea05d953b7/pandas-3.0.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:232a70ebb568c0c4d2db4584f338c1577d81e3af63292208d615907b698a0f18", size = 10347921 }, - { url = "https://files.pythonhosted.org/packages/35/d0/4831af68ce30cc2d03c697bea8450e3225a835ef497d0d70f31b8cdde965/pandas-3.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:970762605cff1ca0d3f71ed4f3a769ea8f85fc8e6348f6e110b8fea7e6eb5a14", size = 9888127 }, - { url = "https://files.pythonhosted.org/packages/61/a9/16ea9346e1fc4a96e2896242d9bc674764fb9049b0044c0132502f7a771e/pandas-3.0.2-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:aff4e6f4d722e0652707d7bcb190c445fe58428500c6d16005b02401764b1b3d", size = 10399577 }, - { url = "https://files.pythonhosted.org/packages/c4/a8/3a61a721472959ab0ce865ef05d10b0d6bfe27ce8801c99f33d4fa996e65/pandas-3.0.2-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ef8b27695c3d3dc78403c9a7d5e59a62d5464a7e1123b4e0042763f7104dc74f", size = 10880030 }, - { url = "https://files.pythonhosted.org/packages/da/65/7225c0ea4d6ce9cb2160a7fb7f39804871049f016e74782e5dade4d14109/pandas-3.0.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:f8d68083e49e16b84734eb1a4dcae4259a75c90fb6e2251ab9a00b61120c06ab", size = 11409468 }, - { url = "https://files.pythonhosted.org/packages/fa/5b/46e7c76032639f2132359b5cf4c785dd8cf9aea5ea64699eac752f02b9db/pandas-3.0.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:32cc41f310ebd4a296d93515fcac312216adfedb1894e879303987b8f1e2b97d", size = 11936381 }, - { url = "https://files.pythonhosted.org/packages/7b/8b/721a9cff6fa6a91b162eb51019c6243b82b3226c71bb6c8ef4a9bd65cbc6/pandas-3.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:a4785e1d6547d8427c5208b748ae2efb64659a21bd82bf440d4262d02bfa02a4", size = 9744993 }, - { url = "https://files.pythonhosted.org/packages/d5/18/7f0bd34ae27b28159aa80f2a6799f47fda34f7fb938a76e20c7b7fe3b200/pandas-3.0.2-cp312-cp312-win_arm64.whl", hash = "sha256:08504503f7101300107ecdc8df73658e4347586db5cfdadabc1592e9d7e7a0fd", size = 9056118 }, - { url = "https://files.pythonhosted.org/packages/bf/ca/3e639a1ea6fcd0617ca4e8ca45f62a74de33a56ae6cd552735470b22c8d3/pandas-3.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b5918ba197c951dec132b0c5929a00c0bf05d5942f590d3c10a807f6e15a57d3", size = 10321105 }, - { url = "https://files.pythonhosted.org/packages/0b/77/dbc82ff2fb0e63c6564356682bf201edff0ba16c98630d21a1fb312a8182/pandas-3.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d606a041c89c0a474a4702d532ab7e73a14fe35c8d427b972a625c8e46373668", size = 9864088 }, - { url = "https://files.pythonhosted.org/packages/5c/2b/341f1b04bbca2e17e13cd3f08c215b70ef2c60c5356ef1e8c6857449edc7/pandas-3.0.2-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:710246ba0616e86891b58ab95f2495143bb2bc83ab6b06747c74216f583a6ac9", size = 10369066 }, - { url = "https://files.pythonhosted.org/packages/12/c5/cbb1ffefb20a93d3f0e1fdcda699fb84976210d411b008f97f48bf6ce27e/pandas-3.0.2-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5d3cfe227c725b1f3dff4278b43d8c784656a42a9325b63af6b1492a8232209e", size = 10876780 }, - { url = "https://files.pythonhosted.org/packages/98/fe/2249ae5e0a69bd0ddf17353d0a5d26611d70970111f5b3600cdc8be883e7/pandas-3.0.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:c3b723df9087a9a9a840e263ebd9f88b64a12075d1bf2ea401a5a42f254f084d", size = 11375181 }, - { url = "https://files.pythonhosted.org/packages/de/64/77a38b09e70b6464883b8d7584ab543e748e42c1b5d337a2ee088e0df741/pandas-3.0.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a3096110bf9eac0070b7208465f2740e2d8a670d5cb6530b5bb884eca495fd39", size = 11928899 }, - { url = "https://files.pythonhosted.org/packages/5e/52/42855bf626868413f761addd574acc6195880ae247a5346477a4361c3acb/pandas-3.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:07a10f5c36512eead51bc578eb3354ad17578b22c013d89a796ab5eee90cd991", size = 9746574 }, - { url = "https://files.pythonhosted.org/packages/88/39/21304ae06a25e8bf9fc820d69b29b2c495b2ae580d1e143146c309941760/pandas-3.0.2-cp313-cp313-win_arm64.whl", hash = "sha256:5fdbfa05931071aba28b408e59226186b01eb5e92bea2ab78b65863ca3228d84", size = 9047156 }, - { url = "https://files.pythonhosted.org/packages/72/20/7defa8b27d4f330a903bb68eea33be07d839c5ea6bdda54174efcec0e1d2/pandas-3.0.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:dbc20dea3b9e27d0e66d74c42b2d0c1bed9c2ffe92adea33633e3bedeb5ac235", size = 10756238 }, - { url = "https://files.pythonhosted.org/packages/e9/95/49433c14862c636afc0e9b2db83ff16b3ad92959364e52b2955e44c8e94c/pandas-3.0.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:b75c347eff42497452116ce05ef461822d97ce5b9ff8df6edacb8076092c855d", size = 10408520 }, - { url = "https://files.pythonhosted.org/packages/3b/f8/462ad2b5881d6b8ec8e5f7ed2ea1893faa02290d13870a1600fe72ad8efc/pandas-3.0.2-cp313-cp313t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d1478075142e83a5571782ad007fb201ed074bdeac7ebcc8890c71442e96adf7", size = 10324154 }, - { url = "https://files.pythonhosted.org/packages/0a/65/d1e69b649cbcddda23ad6e4c40ef935340f6f652a006e5cbc3555ac8adb3/pandas-3.0.2-cp313-cp313t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5880314e69e763d4c8b27937090de570f1fb8d027059a7ada3f7f8e98bdcb677", size = 10714449 }, - { url = "https://files.pythonhosted.org/packages/47/a4/85b59bc65b8190ea3689882db6cdf32a5003c0ccd5a586c30fdcc3ffc4fc/pandas-3.0.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:b5329e26898896f06035241a626d7c335daa479b9bbc82be7c2742d048e41172", size = 11338475 }, - { url = "https://files.pythonhosted.org/packages/1e/c4/bc6966c6e38e5d9478b935272d124d80a589511ed1612a5d21d36f664c68/pandas-3.0.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:81526c4afd31971f8b62671442a4b2b51e0aa9acc3819c9f0f12a28b6fcf85f1", size = 11786568 }, - { url = "https://files.pythonhosted.org/packages/e8/74/09298ca9740beed1d3504e073d67e128aa07e5ca5ca2824b0c674c0b8676/pandas-3.0.2-cp313-cp313t-win_amd64.whl", hash = "sha256:7cadd7e9a44ec13b621aec60f9150e744cfc7a3dd32924a7e2f45edff31823b0", size = 10488652 }, - { url = "https://files.pythonhosted.org/packages/bb/40/c6ea527147c73b24fc15c891c3fcffe9c019793119c5742b8784a062c7db/pandas-3.0.2-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:db0dbfd2a6cdf3770aa60464d50333d8f3d9165b2f2671bcc299b72de5a6677b", size = 10326084 }, - { url = "https://files.pythonhosted.org/packages/95/25/bdb9326c3b5455f8d4d3549fce7abcf967259de146fe2cf7a82368141948/pandas-3.0.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:0555c5882688a39317179ab4a0ed41d3ebc8812ab14c69364bbee8fb7a3f6288", size = 9914146 }, - { url = "https://files.pythonhosted.org/packages/8d/77/3a227ff3337aa376c60d288e1d61c5d097131d0ac71f954d90a8f369e422/pandas-3.0.2-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:01f31a546acd5574ef77fe199bc90b55527c225c20ccda6601cf6b0fd5ed597c", size = 10444081 }, - { url = "https://files.pythonhosted.org/packages/15/88/3cdd54fa279341afa10acf8d2b503556b1375245dccc9315659f795dd2e9/pandas-3.0.2-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:deeca1b5a931fdf0c2212c8a659ade6d3b1edc21f0914ce71ef24456ca7a6535", size = 10897535 }, - { url = "https://files.pythonhosted.org/packages/06/9d/98cc7a7624f7932e40f434299260e2917b090a579d75937cb8a57b9d2de3/pandas-3.0.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:0f48afd9bb13300ffb5a3316973324c787054ba6665cda0da3fbd67f451995db", size = 11446992 }, - { url = "https://files.pythonhosted.org/packages/9a/cd/19ff605cc3760e80602e6826ddef2824d8e7050ed80f2e11c4b079741dc3/pandas-3.0.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:6c4d8458b97a35717b62469a4ea0e85abd5ed8687277f5ccfc67f8a5126f8c53", size = 11968257 }, - { url = "https://files.pythonhosted.org/packages/db/60/aba6a38de456e7341285102bede27514795c1eaa353bc0e7638b6b785356/pandas-3.0.2-cp314-cp314-win_amd64.whl", hash = "sha256:b35d14bb5d8285d9494fe93815a9e9307c0876e10f1e8e89ac5b88f728ec8dcf", size = 9865893 }, - { url = "https://files.pythonhosted.org/packages/08/71/e5ec979dd2e8a093dacb8864598c0ff59a0cee0bbcdc0bfec16a51684d4f/pandas-3.0.2-cp314-cp314-win_arm64.whl", hash = "sha256:63d141b56ef686f7f0d714cfb8de4e320475b86bf4b620aa0b7da89af8cbdbbb", size = 9188644 }, - { url = "https://files.pythonhosted.org/packages/f1/6c/7b45d85db19cae1eb524f2418ceaa9d85965dcf7b764ed151386b7c540f0/pandas-3.0.2-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:140f0cffb1fa2524e874dde5b477d9defe10780d8e9e220d259b2c0874c89d9d", size = 10776246 }, - { url = "https://files.pythonhosted.org/packages/a8/3e/7b00648b086c106e81766f25322b48aa8dfa95b55e621dbdf2fdd413a117/pandas-3.0.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:ae37e833ff4fed0ba352f6bdd8b73ba3ab3256a85e54edfd1ab51ae40cca0af8", size = 10424801 }, - { url = "https://files.pythonhosted.org/packages/da/6e/558dd09a71b53b4008e7fc8a98ec6d447e9bfb63cdaeea10e5eb9b2dabe8/pandas-3.0.2-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4d888a5c678a419a5bb41a2a93818e8ed9fd3172246555c0b37b7cc27027effd", size = 10345643 }, - { url = "https://files.pythonhosted.org/packages/be/e3/921c93b4d9a280409451dc8d07b062b503bbec0531d2627e73a756e99a82/pandas-3.0.2-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b444dc64c079e84df91baa8bf613d58405645461cabca929d9178f2cd392398d", size = 10743641 }, - { url = "https://files.pythonhosted.org/packages/56/ca/fd17286f24fa3b4d067965d8d5d7e14fe557dd4f979a0b068ac0deaf8228/pandas-3.0.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:4544c7a54920de8eeacaa1466a6b7268ecfbc9bc64ab4dbb89c6bbe94d5e0660", size = 11361993 }, - { url = "https://files.pythonhosted.org/packages/e4/a5/2f6ed612056819de445a433ca1f2821ac3dab7f150d569a59e9cc105de1d/pandas-3.0.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:734be7551687c00fbd760dc0522ed974f82ad230d4a10f54bf51b80d44a08702", size = 11815274 }, - { url = "https://files.pythonhosted.org/packages/00/2f/b622683e99ec3ce00b0854bac9e80868592c5b051733f2cf3a868e5fea26/pandas-3.0.2-cp314-cp314t-win_amd64.whl", hash = "sha256:57a07209bebcbcf768d2d13c9b78b852f9a15978dac41b9e6421a81ad4cdd276", size = 10888530 }, - { url = "https://files.pythonhosted.org/packages/cb/2b/f8434233fab2bd66a02ec014febe4e5adced20e2693e0e90a07d118ed30e/pandas-3.0.2-cp314-cp314t-win_arm64.whl", hash = "sha256:5371b72c2d4d415d08765f32d689217a43227484e81b2305b52076e328f6f482", size = 9455341 }, +sdist = { url = "https://files.pythonhosted.org/packages/be/4f/5f3422a2afec5ffc46308b79e53291365a93748b498ac2e58bead0197916/pandas-3.0.5.tar.gz", hash = "sha256:dca3734d6ab7c906e6730f0788b0a1dbb9f2467731f9711f77995c8e9d62d712", size = 4658219, upload-time = "2026-07-22T22:19:28.819Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/48/ef/f1fd7431d635bf20015489bf0bd69c17fff1018de773540f651455a3916b/pandas-3.0.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2946e77e4a53cd248cbde631a12f0e51c8324ce354c3eba4d20147c1ad6f4282", size = 10397178, upload-time = "2026-07-22T22:17:48.274Z" }, + { url = "https://files.pythonhosted.org/packages/31/b4/0eafac990a431561187694126de01f9b12559549b4d86360c0c4bd870fde/pandas-3.0.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:71ecc8fb7ed1a7aa4392316b5309a6347e8e7f832f38fd897846b3a1457a9298", size = 9990736, upload-time = "2026-07-22T22:17:52.388Z" }, + { url = "https://files.pythonhosted.org/packages/de/21/359880af3ea9b7cb23bea5b51e8e70ef3866c03be09da9a2787e18e330a8/pandas-3.0.5-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b173f5951ff6b8b0ec7675e20dff3c97b7e7a57dfcce387c2d7c5afe87cb7899", size = 10814438, upload-time = "2026-07-22T22:17:54.708Z" }, + { url = "https://files.pythonhosted.org/packages/d1/50/d6cc4d7e508bbccf5d6027314a8312bc7ac73d0ec7f195f53838daafab40/pandas-3.0.5-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2c0cf1dd9b55a22d105fc46c1b489af3bd42264fcba7c66297bf47a9a1d9c78a", size = 11323634, upload-time = "2026-07-22T22:17:56.858Z" }, + { url = "https://files.pythonhosted.org/packages/70/2b/d5f0a8c90dd0ae04e64ba53b871afb796ec026b615086d382ddc2ade729b/pandas-3.0.5-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0fac0010c75e4efb6b99e249c183a8993ce0dc95c240f9b120a5e67c727b7928", size = 11850860, upload-time = "2026-07-22T22:17:59.1Z" }, + { url = "https://files.pythonhosted.org/packages/5c/30/183aec2e19adf778a98d29b5729a0a68f4cc4ebf9b9c3b70d0297355bcb1/pandas-3.0.5-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:08d24fe11a17dc33bd6e937dc9c665f9cba08fbdc9f657f405713515febe300d", size = 12411100, upload-time = "2026-07-22T22:18:01.485Z" }, + { url = "https://files.pythonhosted.org/packages/fa/9a/31f4983f191af51ab2a8f2d0c7b33dff3a84da26533f982fff02c2f9e28b/pandas-3.0.5-cp311-cp311-win_amd64.whl", hash = "sha256:b1261758dfb6cf12c3cff8300e21cefad30e7ec709abb4c24ac7318e6a52462a", size = 9968804, upload-time = "2026-07-22T22:18:03.903Z" }, + { url = "https://files.pythonhosted.org/packages/49/97/7886c89a39045c69ad82cbceaf3343810480c8ef49a216319ce8183860a6/pandas-3.0.5-cp311-cp311-win_arm64.whl", hash = "sha256:679f4e85b30ddb1515458ab1e788d3e260eae369b1f78da7a3aa4cac8ebf4a2a", size = 9205447, upload-time = "2026-07-22T22:18:06.134Z" }, + { url = "https://files.pythonhosted.org/packages/1c/54/1dc810ea558d1320b597aa140a514f2fdf1d2ea09c38cf556f13ea712ec9/pandas-3.0.5-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:fa290c16964d4963fbfbc358928239cf3bd755b20e988ce944877def2f44471d", size = 10411717, upload-time = "2026-07-22T22:18:08.307Z" }, + { url = "https://files.pythonhosted.org/packages/68/56/fbe81c09195924d8b7b8d4461a20458fe80a6a5ed6b24f0314da684277e1/pandas-3.0.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c2e26bb46934b8a2ca0c3de1d3d606fc5f6746584791b2db264d58cf370e08dc", size = 9957095, upload-time = "2026-07-22T22:18:10.6Z" }, + { url = "https://files.pythonhosted.org/packages/e0/51/fac252f4a913ed5eabf3c11b880a9e8d5a6c10f0b2129d0462212d238b4d/pandas-3.0.5-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:73fa87b08a7ef706f8aafda39ddaccf2a99047bea62d8c88a0361bcafb2237bc", size = 10485458, upload-time = "2026-07-22T22:18:12.834Z" }, + { url = "https://files.pythonhosted.org/packages/12/98/e976540c1addf70442be7842a18cf70884a964abbf69442504f4d2939989/pandas-3.0.5-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d373ce03ffd84010ed9839fa73672a9c8256990532e158440c0085db7d914b34", size = 10998091, upload-time = "2026-07-22T22:18:15.209Z" }, + { url = "https://files.pythonhosted.org/packages/a4/8c/1f29b5be8d3fc47dd7567eb167fabba2085879b31e0287ce7cba6d3d2ff4/pandas-3.0.5-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2a29c53d85ea98c5e792c59ef82ee9fbe6ca902c0d0adb6b23f45ef894cd7bf6", size = 11499501, upload-time = "2026-07-22T22:18:17.689Z" }, + { url = "https://files.pythonhosted.org/packages/9d/e2/bd9c98ad2df7b38bde002adde4cdf353519da51881634323b126c55997f9/pandas-3.0.5-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a5ad3b02ed6bc7d7ae9b70804b2c6aa31827489d150f8e623ce82491b82085d7", size = 12060559, upload-time = "2026-07-22T22:18:20.147Z" }, + { url = "https://files.pythonhosted.org/packages/f3/9a/ffbd852d58bd74a617fe2f8ee6a58a96982271ce41cf981eab22190b4a4b/pandas-3.0.5-cp312-cp312-pyemscripten_2024_0_wasm32.whl", hash = "sha256:b2acb4650527eec6822c3dadb2b771277b65e7dae7a267d4bccf65fd1bb3fbce", size = 7197652, upload-time = "2026-07-22T22:18:22.502Z" }, + { url = "https://files.pythonhosted.org/packages/70/b5/d2d3e9ae73362ba4229651b0ee1455cf78073a1ce585f6ff693782ce263e/pandas-3.0.5-cp312-cp312-win_amd64.whl", hash = "sha256:80a611068e8a3ac23f7398c6c14eb46dc974e5cc9997f653e2dcfd1da74edd41", size = 9831691, upload-time = "2026-07-22T22:18:24.534Z" }, + { url = "https://files.pythonhosted.org/packages/52/51/dea1e89d6a6796b9c43f85a09b484ee03edb8a4c4842e73e200a8c11301c/pandas-3.0.5-cp312-cp312-win_arm64.whl", hash = "sha256:25ff585b972a18ef1fe9ffa3ac6544d9950508aa76832e5147640b6022821e49", size = 9105796, upload-time = "2026-07-22T22:18:27.064Z" }, + { url = "https://files.pythonhosted.org/packages/bf/09/7b95c4a0025227d6f118c4039b423412ac6a982db02864166185d812fbc7/pandas-3.0.5-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:c1c05a767fe8e5b4fe9e1c29806829c582052eaedb9120a3da83ba3f69e24a5b", size = 10385742, upload-time = "2026-07-22T22:18:29.346Z" }, + { url = "https://files.pythonhosted.org/packages/8d/0c/dc78fd8c4da477b4b5e8ad37295af352190d21ef63a9ee1bc071753074cc/pandas-3.0.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:b86765f268b56f7e665b93bce9d5df69dee7f99e595cf8fb839483ab315942a3", size = 9932067, upload-time = "2026-07-22T22:18:31.833Z" }, + { url = "https://files.pythonhosted.org/packages/3e/71/3592c055cf44df9808550f9368ceda80ff2b224d355ef73fe251dcda1802/pandas-3.0.5-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c597ecf5616b5c420372c1d4d4c00dbbfba7398bea857dcc984347e1ea48417b", size = 10466756, upload-time = "2026-07-22T22:18:34.195Z" }, + { url = "https://files.pythonhosted.org/packages/e3/70/4363150359f95b4cb4bcbb34ca23572bb5495749a621a8f3d5a1ddfd293c/pandas-3.0.5-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4b11c36e218331d0387cbe3a0a5f75162357a1d92d57b2b08a336ff94b19b2be", size = 10938525, upload-time = "2026-07-22T22:18:36.81Z" }, + { url = "https://files.pythonhosted.org/packages/f7/d0/317e7a0c67c0e69fa905a0161409397a7dc2d46ff611f6ca4803352c042b/pandas-3.0.5-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cf52e1f61d229496da17dc7ab54acdee627357e7008fd4fecba3d0ba2937fa58", size = 11489303, upload-time = "2026-07-22T22:18:39.287Z" }, + { url = "https://files.pythonhosted.org/packages/f1/8d/36dade89b49e4f9d5cbdbe863772581f98c0c6d78fc39ad4c557f6f2e17e/pandas-3.0.5-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:db172144bb56422bd157812f3b021eacc255451470b31e2c633c349490a1cfee", size = 11989004, upload-time = "2026-07-22T22:18:42.208Z" }, + { url = "https://files.pythonhosted.org/packages/9c/ba/18c4ec8a746e177da05a9e7a7963781d8ea195780724f854601b6ebd6b78/pandas-3.0.5-cp313-cp313-win_amd64.whl", hash = "sha256:0d298e951f23016ce4699951d044ae6418dbc91bf68cefca0f77666fcbb4e5c6", size = 9826896, upload-time = "2026-07-22T22:18:44.539Z" }, + { url = "https://files.pythonhosted.org/packages/de/ec/28a57266b753799a87b8bc79e7887ac6fd981b8c6d2978a0b7e7b6bd708c/pandas-3.0.5-cp313-cp313-win_arm64.whl", hash = "sha256:66266d3442a5e8b3c90274c2b8b230bee42dd1c286bc822cc2f9f2c7e12b883e", size = 9094790, upload-time = "2026-07-22T22:18:47.468Z" }, + { url = "https://files.pythonhosted.org/packages/51/2f/cf6aae281264f4463f0875bcbb15fd2bb6d291cc535187dad1732475e4a9/pandas-3.0.5-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:2f264fc46911cc8131a7322a16199bbf8e353d27c10bb211f5bd0c814324dc36", size = 10390034, upload-time = "2026-07-22T22:18:49.818Z" }, + { url = "https://files.pythonhosted.org/packages/06/ec/5189518c7a7659c4bdcc6b1eb32c46c6f3c86b0661ffd84143d1112c7732/pandas-3.0.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:53730687fcd161883b24e10411c06d6a4c0f2275d2faf3bb2bc25deb4ba8007c", size = 9980065, upload-time = "2026-07-22T22:18:52.249Z" }, + { url = "https://files.pythonhosted.org/packages/ea/f1/598503ce8d7e3c35601e0747ba288c7864baae66380725bc12f13f884dfe/pandas-3.0.5-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:960d3ebcf249f75206899fcd2c6de53f736b7265759ced0d3e559df0b8b709b0", size = 10545532, upload-time = "2026-07-22T22:18:54.813Z" }, + { url = "https://files.pythonhosted.org/packages/fa/de/ceae2adf7034e07e9910299fe412e1819c4f0dd520700a888bcb03625448/pandas-3.0.5-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9e94c2c5ca43bd3ca32bf64d32308887b65e5f9bfd8023ea52755107a999f93b", size = 10963120, upload-time = "2026-07-22T22:18:57.42Z" }, + { url = "https://files.pythonhosted.org/packages/66/25/86e0f4451874eb79e688deeebe3c451fec4557f8952005818d800ee8ac7e/pandas-3.0.5-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:e819dd5f62966b481a8cb649d3299ebd886a1ea91ed5a99bf7ce77c98d18ab94", size = 11563178, upload-time = "2026-07-22T22:18:59.729Z" }, + { url = "https://files.pythonhosted.org/packages/f3/45/8643daa3b4147e433adfcccefdd0380d3aad79d86b15d8999730fe1944d5/pandas-3.0.5-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:3c5ed2e7c06e91d340dfd091d7934f9bc82e4a36b95f647f090b9d1c9ac649da", size = 12028708, upload-time = "2026-07-22T22:19:02.164Z" }, + { url = "https://files.pythonhosted.org/packages/96/58/ad979ae617615576e8aafd569c9d4b62f1191d896e38f51d66ba06f3b89a/pandas-3.0.5-cp314-cp314-win_amd64.whl", hash = "sha256:cd8f7c6dc98527058ee6264219343f5392240a6f1bfa654fc5d79023020d0c92", size = 9951806, upload-time = "2026-07-22T22:19:04.596Z" }, + { url = "https://files.pythonhosted.org/packages/69/32/7ac03886b304049a9d2625ee88f59af760d8a93bd30ed9239bce7b9869a8/pandas-3.0.5-cp314-cp314-win_arm64.whl", hash = "sha256:5183427f5a8156d480f30333777bc978be93650a49a7c01db26adffe95b31e85", size = 9238297, upload-time = "2026-07-22T22:19:06.836Z" }, + { url = "https://files.pythonhosted.org/packages/be/ed/1d1f2ee5547d5167face2376d11c8b2a4c7bfff5a416ee7a9046891fab1e/pandas-3.0.5-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:303da736987d481074ca720ada325f8bd80c64ebc2d45ed79b29df3aaa4a26ca", size = 10849690, upload-time = "2026-07-22T22:19:09.391Z" }, + { url = "https://files.pythonhosted.org/packages/57/55/17e17152e98fbb0c4b1e562bc65387a2f20a80db0f4a86bf8d3a0e4248d4/pandas-3.0.5-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:3b2801bbb049d0136f6c213eae02b5fca969384fc2064dd728d8620552aa49da", size = 10509945, upload-time = "2026-07-22T22:19:11.773Z" }, + { url = "https://files.pythonhosted.org/packages/88/90/817d44dbf83facf9556f33576d9af0a241981e7bb5c00606c0bcb5df8dda/pandas-3.0.5-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cce3a9d11d2b1f82c69a27ec1f4948a170e2c403c4bbfa8cca62e3fdebe2ef3a", size = 10392197, upload-time = "2026-07-22T22:19:14.024Z" }, + { url = "https://files.pythonhosted.org/packages/f1/da/889f00c0a6f5aa1545add70abbf01502dff87ab577adb855bd631c54d2f2/pandas-3.0.5-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ef01af4d8dc6cd2c8d6c7736f149574ef93fe043811eeb5e445f2647154b5040", size = 10862726, upload-time = "2026-07-22T22:19:16.351Z" }, + { url = "https://files.pythonhosted.org/packages/bc/98/f1e934fb3c98fce859c6147c6785816c7b5b9ab7821115c5d8c4de9842b9/pandas-3.0.5-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e2759e890db96dfcffdbd9b86c3c2cb6afaf58def482820317e06163ec1066cd", size = 11414864, upload-time = "2026-07-22T22:19:18.981Z" }, + { url = "https://files.pythonhosted.org/packages/fe/be/d448af7d657d82e1888dd8551f79c6d6fb161080b5b9752d84d910ec2319/pandas-3.0.5-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:b58b1b39d46a5862e3fb18f50d1a201398619d16a0f9f73f57eea5583cf0e63c", size = 11925105, upload-time = "2026-07-22T22:19:21.515Z" }, + { url = "https://files.pythonhosted.org/packages/29/c1/ccb4238212c8c4f496c584f3044d94e0c030ed8e1d68999db46c91c2242f/pandas-3.0.5-cp314-cp314t-win_amd64.whl", hash = "sha256:1c10461f6eeb35d8f05b6184c65c8b9991663b66c46b1d559b682cb34ae7c6ea", size = 10387612, upload-time = "2026-07-22T22:19:24.257Z" }, + { url = "https://files.pythonhosted.org/packages/d2/cf/6a51b2c38980e04c279fd2fa908a1b0982064e860444acfca4ec2e2c8359/pandas-3.0.5-cp314-cp314t-win_arm64.whl", hash = "sha256:3c5015fd1730fbf883647e88068176c839c102cea883ba1769a6f4593bfc1f8c", size = 9509776, upload-time = "2026-07-22T22:19:26.694Z" }, +] + +[[package]] +name = "pathspec" +version = "1.1.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/5a/82/42f767fc1c1143d6fd36efb827202a2d997a375e160a71eb2888a925aac1/pathspec-1.1.1.tar.gz", hash = "sha256:17db5ecd524104a120e173814c90367a96a98d07c45b2e10c2f3919fff91bf5a", size = 135180, upload-time = "2026-04-27T01:46:08.907Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f1/d9/7fb5aa316bc299258e68c73ba3bddbc499654a07f151cba08f6153988714/pathspec-1.1.1-py3-none-any.whl", hash = "sha256:a00ce642f577bf7f473932318056212bc4f8bfdf53128c78bbd5af0b9b20b189", size = 57328, upload-time = "2026-04-27T01:46:07.06Z" }, ] [[package]] name = "pillow" -version = "12.2.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/8c/21/c2bcdd5906101a30244eaffc1b6e6ce71a31bd0742a01eb89e660ebfac2d/pillow-12.2.0.tar.gz", hash = "sha256:a830b1a40919539d07806aa58e1b114df53ddd43213d9c8b75847eee6c0182b5", size = 46987819 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/68/e1/748f5663efe6edcfc4e74b2b93edfb9b8b99b67f21a854c3ae416500a2d9/pillow-12.2.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:8be29e59487a79f173507c30ddf57e733a357f67881430449bb32614075a40ab", size = 5354347 }, - { url = "https://files.pythonhosted.org/packages/47/a1/d5ff69e747374c33a3b53b9f98cca7889fce1fd03d79cdc4e1bccc6c5a87/pillow-12.2.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:71cde9a1e1551df7d34a25462fc60325e8a11a82cc2e2f54578e5e9a1e153d65", size = 4695873 }, - { url = "https://files.pythonhosted.org/packages/df/21/e3fbdf54408a973c7f7f89a23b2cb97a7ef30c61ab4142af31eee6aebc88/pillow-12.2.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f490f9368b6fc026f021db16d7ec2fbf7d89e2edb42e8ec09d2c60505f5729c7", size = 6280168 }, - { url = "https://files.pythonhosted.org/packages/d3/f1/00b7278c7dd52b17ad4329153748f87b6756ec195ff786c2bdf12518337d/pillow-12.2.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8bd7903a5f2a4545f6fd5935c90058b89d30045568985a71c79f5fd6edf9b91e", size = 8088188 }, - { url = "https://files.pythonhosted.org/packages/ad/cf/220a5994ef1b10e70e85748b75649d77d506499352be135a4989c957b701/pillow-12.2.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3997232e10d2920a68d25191392e3a4487d8183039e1c74c2297f00ed1c50705", size = 6394401 }, - { url = "https://files.pythonhosted.org/packages/e9/bd/e51a61b1054f09437acfbc2ff9106c30d1eb76bc1453d428399946781253/pillow-12.2.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e74473c875d78b8e9d5da2a70f7099549f9eb37ded4e2f6a463e60125bccd176", size = 7079655 }, - { url = "https://files.pythonhosted.org/packages/6b/3d/45132c57d5fb4b5744567c3817026480ac7fc3ce5d4c47902bc0e7f6f853/pillow-12.2.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:56a3f9c60a13133a98ecff6197af34d7824de9b7b38c3654861a725c970c197b", size = 6503105 }, - { url = "https://files.pythonhosted.org/packages/7d/2e/9df2fc1e82097b1df3dce58dc43286aa01068e918c07574711fcc53e6fb4/pillow-12.2.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:90e6f81de50ad6b534cab6e5aef77ff6e37722b2f5d908686f4a5c9eba17a909", size = 7203402 }, - { url = "https://files.pythonhosted.org/packages/bd/2e/2941e42858ebb67e50ae741473de81c2984e6eff7b397017623c676e2e8d/pillow-12.2.0-cp311-cp311-win32.whl", hash = "sha256:8c984051042858021a54926eb597d6ee3012393ce9c181814115df4c60b9a808", size = 6378149 }, - { url = "https://files.pythonhosted.org/packages/69/42/836b6f3cd7f3e5fa10a1f1a5420447c17966044c8fbf589cc0452d5502db/pillow-12.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:6e6b2a0c538fc200b38ff9eb6628228b77908c319a005815f2dde585a0664b60", size = 7082626 }, - { url = "https://files.pythonhosted.org/packages/c2/88/549194b5d6f1f494b485e493edc6693c0a16f4ada488e5bd974ed1f42fad/pillow-12.2.0-cp311-cp311-win_arm64.whl", hash = "sha256:9a8a34cc89c67a65ea7437ce257cea81a9dad65b29805f3ecee8c8fe8ff25ffe", size = 2463531 }, - { url = "https://files.pythonhosted.org/packages/58/be/7482c8a5ebebbc6470b3eb791812fff7d5e0216c2be3827b30b8bb6603ed/pillow-12.2.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:2d192a155bbcec180f8564f693e6fd9bccff5a7af9b32e2e4bf8c9c69dbad6b5", size = 5308279 }, - { url = "https://files.pythonhosted.org/packages/d8/95/0a351b9289c2b5cbde0bacd4a83ebc44023e835490a727b2a3bd60ddc0f4/pillow-12.2.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f3f40b3c5a968281fd507d519e444c35f0ff171237f4fdde090dd60699458421", size = 4695490 }, - { url = "https://files.pythonhosted.org/packages/de/af/4e8e6869cbed569d43c416fad3dc4ecb944cb5d9492defaed89ddd6fe871/pillow-12.2.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:03e7e372d5240cc23e9f07deca4d775c0817bffc641b01e9c3af208dbd300987", size = 6284462 }, - { url = "https://files.pythonhosted.org/packages/e9/9e/c05e19657fd57841e476be1ab46c4d501bffbadbafdc31a6d665f8b737b6/pillow-12.2.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:b86024e52a1b269467a802258c25521e6d742349d760728092e1bc2d135b4d76", size = 8094744 }, - { url = "https://files.pythonhosted.org/packages/2b/54/1789c455ed10176066b6e7e6da1b01e50e36f94ba584dc68d9eebfe9156d/pillow-12.2.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7371b48c4fa448d20d2714c9a1f775a81155050d383333e0a6c15b1123dda005", size = 6398371 }, - { url = "https://files.pythonhosted.org/packages/43/e3/fdc657359e919462369869f1c9f0e973f353f9a9ee295a39b1fea8ee1a77/pillow-12.2.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:62f5409336adb0663b7caa0da5c7d9e7bdbaae9ce761d34669420c2a801b2780", size = 7087215 }, - { url = "https://files.pythonhosted.org/packages/8b/f8/2f6825e441d5b1959d2ca5adec984210f1ec086435b0ed5f52c19b3b8a6e/pillow-12.2.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:01afa7cf67f74f09523699b4e88c73fb55c13346d212a59a2db1f86b0a63e8c5", size = 6509783 }, - { url = "https://files.pythonhosted.org/packages/67/f9/029a27095ad20f854f9dba026b3ea6428548316e057e6fc3545409e86651/pillow-12.2.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fc3d34d4a8fbec3e88a79b92e5465e0f9b842b628675850d860b8bd300b159f5", size = 7212112 }, - { url = "https://files.pythonhosted.org/packages/be/42/025cfe05d1be22dbfdb4f264fe9de1ccda83f66e4fc3aac94748e784af04/pillow-12.2.0-cp312-cp312-win32.whl", hash = "sha256:58f62cc0f00fd29e64b29f4fd923ffdb3859c9f9e6105bfc37ba1d08994e8940", size = 6378489 }, - { url = "https://files.pythonhosted.org/packages/5d/7b/25a221d2c761c6a8ae21bfa3874988ff2583e19cf8a27bf2fee358df7942/pillow-12.2.0-cp312-cp312-win_amd64.whl", hash = "sha256:7f84204dee22a783350679a0333981df803dac21a0190d706a50475e361c93f5", size = 7084129 }, - { url = "https://files.pythonhosted.org/packages/10/e1/542a474affab20fd4a0f1836cb234e8493519da6b76899e30bcc5d990b8b/pillow-12.2.0-cp312-cp312-win_arm64.whl", hash = "sha256:af73337013e0b3b46f175e79492d96845b16126ddf79c438d7ea7ff27783a414", size = 2463612 }, - { url = "https://files.pythonhosted.org/packages/4a/01/53d10cf0dbad820a8db274d259a37ba50b88b24768ddccec07355382d5ad/pillow-12.2.0-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:8297651f5b5679c19968abefd6bb84d95fe30ef712eb1b2d9b2d31ca61267f4c", size = 4100837 }, - { url = "https://files.pythonhosted.org/packages/0f/98/f3a6657ecb698c937f6c76ee564882945f29b79bad496abcba0e84659ec5/pillow-12.2.0-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:50d8520da2a6ce0af445fa6d648c4273c3eeefbc32d7ce049f22e8b5c3daecc2", size = 4176528 }, - { url = "https://files.pythonhosted.org/packages/69/bc/8986948f05e3ea490b8442ea1c1d4d990b24a7e43d8a51b2c7d8b1dced36/pillow-12.2.0-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:766cef22385fa1091258ad7e6216792b156dc16d8d3fa607e7545b2b72061f1c", size = 3640401 }, - { url = "https://files.pythonhosted.org/packages/34/46/6c717baadcd62bc8ed51d238d521ab651eaa74838291bda1f86fe1f864c9/pillow-12.2.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5d2fd0fa6b5d9d1de415060363433f28da8b1526c1c129020435e186794b3795", size = 5308094 }, - { url = "https://files.pythonhosted.org/packages/71/43/905a14a8b17fdb1ccb58d282454490662d2cb89a6bfec26af6d3520da5ec/pillow-12.2.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:56b25336f502b6ed02e889f4ece894a72612fe885889a6e8c4c80239ff6e5f5f", size = 4695402 }, - { url = "https://files.pythonhosted.org/packages/73/dd/42107efcb777b16fa0393317eac58f5b5cf30e8392e266e76e51cff28c3d/pillow-12.2.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f1c943e96e85df3d3478f7b691f229887e143f81fedab9b20205349ab04d73ed", size = 6280005 }, - { url = "https://files.pythonhosted.org/packages/a8/68/b93e09e5e8549019e61acf49f65b1a8530765a7f812c77a7461bca7e4494/pillow-12.2.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:03f6fab9219220f041c74aeaa2939ff0062bd5c364ba9ce037197f4c6d498cd9", size = 8090669 }, - { url = "https://files.pythonhosted.org/packages/4b/6e/3ccb54ce8ec4ddd1accd2d89004308b7b0b21c4ac3d20fa70af4760a4330/pillow-12.2.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5cdfebd752ec52bf5bb4e35d9c64b40826bc5b40a13df7c3cda20a2c03a0f5ed", size = 6395194 }, - { url = "https://files.pythonhosted.org/packages/67/ee/21d4e8536afd1a328f01b359b4d3997b291ffd35a237c877b331c1c3b71c/pillow-12.2.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:eedf4b74eda2b5a4b2b2fb4c006d6295df3bf29e459e198c90ea48e130dc75c3", size = 7082423 }, - { url = "https://files.pythonhosted.org/packages/78/5f/e9f86ab0146464e8c133fe85df987ed9e77e08b29d8d35f9f9f4d6f917ba/pillow-12.2.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:00a2865911330191c0b818c59103b58a5e697cae67042366970a6b6f1b20b7f9", size = 6505667 }, - { url = "https://files.pythonhosted.org/packages/ed/1e/409007f56a2fdce61584fd3acbc2bbc259857d555196cedcadc68c015c82/pillow-12.2.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:1e1757442ed87f4912397c6d35a0db6a7b52592156014706f17658ff58bbf795", size = 7208580 }, - { url = "https://files.pythonhosted.org/packages/23/c4/7349421080b12fb35414607b8871e9534546c128a11965fd4a7002ccfbee/pillow-12.2.0-cp313-cp313-win32.whl", hash = "sha256:144748b3af2d1b358d41286056d0003f47cb339b8c43a9ea42f5fea4d8c66b6e", size = 6375896 }, - { url = "https://files.pythonhosted.org/packages/3f/82/8a3739a5e470b3c6cbb1d21d315800d8e16bff503d1f16b03a4ec3212786/pillow-12.2.0-cp313-cp313-win_amd64.whl", hash = "sha256:390ede346628ccc626e5730107cde16c42d3836b89662a115a921f28440e6a3b", size = 7081266 }, - { url = "https://files.pythonhosted.org/packages/c3/25/f968f618a062574294592f668218f8af564830ccebdd1fa6200f598e65c5/pillow-12.2.0-cp313-cp313-win_arm64.whl", hash = "sha256:8023abc91fba39036dbce14a7d6535632f99c0b857807cbbbf21ecc9f4717f06", size = 2463508 }, - { url = "https://files.pythonhosted.org/packages/4d/a4/b342930964e3cb4dce5038ae34b0eab4653334995336cd486c5a8c25a00c/pillow-12.2.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:042db20a421b9bafecc4b84a8b6e444686bd9d836c7fd24542db3e7df7baad9b", size = 5309927 }, - { url = "https://files.pythonhosted.org/packages/9f/de/23198e0a65a9cf06123f5435a5d95cea62a635697f8f03d134d3f3a96151/pillow-12.2.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:dd025009355c926a84a612fecf58bb315a3f6814b17ead51a8e48d3823d9087f", size = 4698624 }, - { url = "https://files.pythonhosted.org/packages/01/a6/1265e977f17d93ea37aa28aa81bad4fa597933879fac2520d24e021c8da3/pillow-12.2.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:88ddbc66737e277852913bd1e07c150cc7bb124539f94c4e2df5344494e0a612", size = 6321252 }, - { url = "https://files.pythonhosted.org/packages/3c/83/5982eb4a285967baa70340320be9f88e57665a387e3a53a7f0db8231a0cd/pillow-12.2.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d362d1878f00c142b7e1a16e6e5e780f02be8195123f164edf7eddd911eefe7c", size = 8126550 }, - { url = "https://files.pythonhosted.org/packages/4e/48/6ffc514adce69f6050d0753b1a18fd920fce8cac87620d5a31231b04bfc5/pillow-12.2.0-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2c727a6d53cb0018aadd8018c2b938376af27914a68a492f59dfcaca650d5eea", size = 6433114 }, - { url = "https://files.pythonhosted.org/packages/36/a3/f9a77144231fb8d40ee27107b4463e205fa4677e2ca2548e14da5cf18dce/pillow-12.2.0-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:efd8c21c98c5cc60653bcb311bef2ce0401642b7ce9d09e03a7da87c878289d4", size = 7115667 }, - { url = "https://files.pythonhosted.org/packages/c1/fc/ac4ee3041e7d5a565e1c4fd72a113f03b6394cc72ab7089d27608f8aaccb/pillow-12.2.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9f08483a632889536b8139663db60f6724bfcb443c96f1b18855860d7d5c0fd4", size = 6538966 }, - { url = "https://files.pythonhosted.org/packages/c0/a8/27fb307055087f3668f6d0a8ccb636e7431d56ed0750e07a60547b1e083e/pillow-12.2.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:dac8d77255a37e81a2efcbd1fc05f1c15ee82200e6c240d7e127e25e365c39ea", size = 7238241 }, - { url = "https://files.pythonhosted.org/packages/ad/4b/926ab182c07fccae9fcb120043464e1ff1564775ec8864f21a0ebce6ac25/pillow-12.2.0-cp313-cp313t-win32.whl", hash = "sha256:ee3120ae9dff32f121610bb08e4313be87e03efeadfc6c0d18f89127e24d0c24", size = 6379592 }, - { url = "https://files.pythonhosted.org/packages/c2/c4/f9e476451a098181b30050cc4c9a3556b64c02cf6497ea421ac047e89e4b/pillow-12.2.0-cp313-cp313t-win_amd64.whl", hash = "sha256:325ca0528c6788d2a6c3d40e3568639398137346c3d6e66bb61db96b96511c98", size = 7085542 }, - { url = "https://files.pythonhosted.org/packages/00/a4/285f12aeacbe2d6dc36c407dfbbe9e96d4a80b0fb710a337f6d2ad978c75/pillow-12.2.0-cp313-cp313t-win_arm64.whl", hash = "sha256:2e5a76d03a6c6dcef67edabda7a52494afa4035021a79c8558e14af25313d453", size = 2465765 }, - { url = "https://files.pythonhosted.org/packages/bf/98/4595daa2365416a86cb0d495248a393dfc84e96d62ad080c8546256cb9c0/pillow-12.2.0-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:3adc9215e8be0448ed6e814966ecf3d9952f0ea40eb14e89a102b87f450660d8", size = 4100848 }, - { url = "https://files.pythonhosted.org/packages/0b/79/40184d464cf89f6663e18dfcf7ca21aae2491fff1a16127681bf1fa9b8cf/pillow-12.2.0-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:6a9adfc6d24b10f89588096364cc726174118c62130c817c2837c60cf08a392b", size = 4176515 }, - { url = "https://files.pythonhosted.org/packages/b0/63/703f86fd4c422a9cf722833670f4f71418fb116b2853ff7da722ea43f184/pillow-12.2.0-cp314-cp314-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:6a6e67ea2e6feda684ed370f9a1c52e7a243631c025ba42149a2cc5934dec295", size = 3640159 }, - { url = "https://files.pythonhosted.org/packages/71/e0/fb22f797187d0be2270f83500aab851536101b254bfa1eae10795709d283/pillow-12.2.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:2bb4a8d594eacdfc59d9e5ad972aa8afdd48d584ffd5f13a937a664c3e7db0ed", size = 5312185 }, - { url = "https://files.pythonhosted.org/packages/ba/8c/1a9e46228571de18f8e28f16fabdfc20212a5d019f3e3303452b3f0a580d/pillow-12.2.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:80b2da48193b2f33ed0c32c38140f9d3186583ce7d516526d462645fd98660ae", size = 4695386 }, - { url = "https://files.pythonhosted.org/packages/70/62/98f6b7f0c88b9addd0e87c217ded307b36be024d4ff8869a812b241d1345/pillow-12.2.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:22db17c68434de69d8ecfc2fe821569195c0c373b25cccb9cbdacf2c6e53c601", size = 6280384 }, - { url = "https://files.pythonhosted.org/packages/5e/03/688747d2e91cfbe0e64f316cd2e8005698f76ada3130d0194664174fa5de/pillow-12.2.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7b14cc0106cd9aecda615dd6903840a058b4700fcb817687d0ee4fc8b6e389be", size = 8091599 }, - { url = "https://files.pythonhosted.org/packages/f6/35/577e22b936fcdd66537329b33af0b4ccfefaeabd8aec04b266528cddb33c/pillow-12.2.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8cbeb542b2ebc6fcdacabf8aca8c1a97c9b3ad3927d46b8723f9d4f033288a0f", size = 6396021 }, - { url = "https://files.pythonhosted.org/packages/11/8d/d2532ad2a603ca2b93ad9f5135732124e57811d0168155852f37fbce2458/pillow-12.2.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4bfd07bc812fbd20395212969e41931001fd59eb55a60658b0e5710872e95286", size = 7083360 }, - { url = "https://files.pythonhosted.org/packages/5e/26/d325f9f56c7e039034897e7380e9cc202b1e368bfd04d4cbe6a441f02885/pillow-12.2.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:9aba9a17b623ef750a4d11b742cbafffeb48a869821252b30ee21b5e91392c50", size = 6507628 }, - { url = "https://files.pythonhosted.org/packages/5f/f7/769d5632ffb0988f1c5e7660b3e731e30f7f8ec4318e94d0a5d674eb65a4/pillow-12.2.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:deede7c263feb25dba4e82ea23058a235dcc2fe1f6021025dc71f2b618e26104", size = 7209321 }, - { url = "https://files.pythonhosted.org/packages/6a/7a/c253e3c645cd47f1aceea6a8bacdba9991bf45bb7dfe927f7c893e89c93c/pillow-12.2.0-cp314-cp314-win32.whl", hash = "sha256:632ff19b2778e43162304d50da0181ce24ac5bb8180122cbe1bf4673428328c7", size = 6479723 }, - { url = "https://files.pythonhosted.org/packages/cd/8b/601e6566b957ca50e28725cb6c355c59c2c8609751efbecd980db44e0349/pillow-12.2.0-cp314-cp314-win_amd64.whl", hash = "sha256:4e6c62e9d237e9b65fac06857d511e90d8461a32adcc1b9065ea0c0fa3a28150", size = 7217400 }, - { url = "https://files.pythonhosted.org/packages/d6/94/220e46c73065c3e2951bb91c11a1fb636c8c9ad427ac3ce7d7f3359b9b2f/pillow-12.2.0-cp314-cp314-win_arm64.whl", hash = "sha256:b1c1fbd8a5a1af3412a0810d060a78b5136ec0836c8a4ef9aa11807f2a22f4e1", size = 2554835 }, - { url = "https://files.pythonhosted.org/packages/b6/ab/1b426a3974cb0e7da5c29ccff4807871d48110933a57207b5a676cccc155/pillow-12.2.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:57850958fe9c751670e49b2cecf6294acc99e562531f4bd317fa5ddee2068463", size = 5314225 }, - { url = "https://files.pythonhosted.org/packages/19/1e/dce46f371be2438eecfee2a1960ee2a243bbe5e961890146d2dee1ff0f12/pillow-12.2.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:d5d38f1411c0ed9f97bcb49b7bd59b6b7c314e0e27420e34d99d844b9ce3b6f3", size = 4698541 }, - { url = "https://files.pythonhosted.org/packages/55/c3/7fbecf70adb3a0c33b77a300dc52e424dc22ad8cdc06557a2e49523b703d/pillow-12.2.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:5c0a9f29ca8e79f09de89293f82fc9b0270bb4af1d58bc98f540cc4aedf03166", size = 6322251 }, - { url = "https://files.pythonhosted.org/packages/1c/3c/7fbc17cfb7e4fe0ef1642e0abc17fc6c94c9f7a16be41498e12e2ba60408/pillow-12.2.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1610dd6c61621ae1cf811bef44d77e149ce3f7b95afe66a4512f8c59f25d9ebe", size = 8127807 }, - { url = "https://files.pythonhosted.org/packages/ff/c3/a8ae14d6defd2e448493ff512fae903b1e9bd40b72efb6ec55ce0048c8ce/pillow-12.2.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0a34329707af4f73cf1782a36cd2289c0368880654a2c11f027bcee9052d35dd", size = 6433935 }, - { url = "https://files.pythonhosted.org/packages/6e/32/2880fb3a074847ac159d8f902cb43278a61e85f681661e7419e6596803ed/pillow-12.2.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8e9c4f5b3c546fa3458a29ab22646c1c6c787ea8f5ef51300e5a60300736905e", size = 7116720 }, - { url = "https://files.pythonhosted.org/packages/46/87/495cc9c30e0129501643f24d320076f4cc54f718341df18cc70ec94c44e1/pillow-12.2.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:fb043ee2f06b41473269765c2feae53fc2e2fbf96e5e22ca94fb5ad677856f06", size = 6540498 }, - { url = "https://files.pythonhosted.org/packages/18/53/773f5edca692009d883a72211b60fdaf8871cbef075eaa9d577f0a2f989e/pillow-12.2.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:f278f034eb75b4e8a13a54a876cc4a5ab39173d2cdd93a638e1b467fc545ac43", size = 7239413 }, - { url = "https://files.pythonhosted.org/packages/c9/e4/4b64a97d71b2a83158134abbb2f5bd3f8a2ea691361282f010998f339ec7/pillow-12.2.0-cp314-cp314t-win32.whl", hash = "sha256:6bb77b2dcb06b20f9f4b4a8454caa581cd4dd0643a08bacf821216a16d9c8354", size = 6482084 }, - { url = "https://files.pythonhosted.org/packages/ba/13/306d275efd3a3453f72114b7431c877d10b1154014c1ebbedd067770d629/pillow-12.2.0-cp314-cp314t-win_amd64.whl", hash = "sha256:6562ace0d3fb5f20ed7290f1f929cae41b25ae29528f2af1722966a0a02e2aa1", size = 7225152 }, - { url = "https://files.pythonhosted.org/packages/ff/6e/cf826fae916b8658848d7b9f38d88da6396895c676e8086fc0988073aaf8/pillow-12.2.0-cp314-cp314t-win_arm64.whl", hash = "sha256:aa88ccfe4e32d362816319ed727a004423aab09c5cea43c01a4b435643fa34eb", size = 2556579 }, - { url = "https://files.pythonhosted.org/packages/4e/b7/2437044fb910f499610356d1352e3423753c98e34f915252aafecc64889f/pillow-12.2.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:0538bd5e05efec03ae613fd89c4ce0368ecd2ba239cc25b9f9be7ed426b0af1f", size = 5273969 }, - { url = "https://files.pythonhosted.org/packages/f6/f4/8316e31de11b780f4ac08ef3654a75555e624a98db1056ecb2122d008d5a/pillow-12.2.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:394167b21da716608eac917c60aa9b969421b5dcbbe02ae7f013e7b85811c69d", size = 4659674 }, - { url = "https://files.pythonhosted.org/packages/d4/37/664fca7201f8bb2aa1d20e2c3d5564a62e6ae5111741966c8319ca802361/pillow-12.2.0-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:5d04bfa02cc2d23b497d1e90a0f927070043f6cbf303e738300532379a4b4e0f", size = 5288479 }, - { url = "https://files.pythonhosted.org/packages/49/62/5b0ed78fce87346be7a5cfcfaaad91f6a1f98c26f86bdbafa2066c647ef6/pillow-12.2.0-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:0c838a5125cee37e68edec915651521191cef1e6aa336b855f495766e77a366e", size = 7032230 }, - { url = "https://files.pythonhosted.org/packages/c3/28/ec0fc38107fc32536908034e990c47914c57cd7c5a3ece4d8d8f7ffd7e27/pillow-12.2.0-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4a6c9fa44005fa37a91ebfc95d081e8079757d2e904b27103f4f5fa6f0bf78c0", size = 5355404 }, - { url = "https://files.pythonhosted.org/packages/5e/8b/51b0eddcfa2180d60e41f06bd6d0a62202b20b59c68f5a132e615b75aecf/pillow-12.2.0-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:25373b66e0dd5905ed63fa3cae13c82fbddf3079f2c8bf15c6fb6a35586324c1", size = 6002215 }, - { url = "https://files.pythonhosted.org/packages/bc/60/5382c03e1970de634027cee8e1b7d39776b778b81812aaf45b694dfe9e28/pillow-12.2.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:bfa9c230d2fe991bed5318a5f119bd6780cda2915cca595393649fc118ab895e", size = 7080946 }, +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 = "platformdirs" -version = "4.9.6" +version = "4.11.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/9f/4a/0883b8e3802965322523f0b200ecf33d31f10991d0401162f4b23c698b42/platformdirs-4.9.6.tar.gz", hash = "sha256:3bfa75b0ad0db84096ae777218481852c0ebc6c727b3168c1b9e0118e458cf0a", size = 29400 } +sdist = { url = "https://files.pythonhosted.org/packages/78/9b/560e4be8e26f6fd133a03630a8df0c663b9e8d61b4ade152b72005aec83b/platformdirs-4.11.0.tar.gz", hash = "sha256:0555d18370482847566ffabcaa53ad7c6c1c29f195989ae1ed634a05f76ea1e0", size = 31953, upload-time = "2026-07-21T13:09:36.565Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/75/a6/a0a304dc33b49145b21f4808d763822111e67d1c3a32b524a1baf947b6e1/platformdirs-4.9.6-py3-none-any.whl", hash = "sha256:e61adb1d5e5cb3441b4b7710bea7e4c12250ca49439228cc1021c00dcfac0917", size = 21348 }, + { url = "https://files.pythonhosted.org/packages/7d/68/d8d58938dfb1370b266a1a729e6d77a985be23689a0496498ee17b2cbf90/platformdirs-4.11.0-py3-none-any.whl", hash = "sha256:360ccded2b7fce0af0ff80cc8f5942a1c5d99b0e856033acb030bfc634709e74", size = 23247, upload-time = "2026-07-21T13:09:35.422Z" }, ] [[package]] name = "pluggy" version = "1.6.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412 } +sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538 }, + { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" }, ] [[package]] name = "pre-commit" -version = "4.5.1" +version = "4.6.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "cfgv" }, @@ -1518,65 +1884,65 @@ dependencies = [ { name = "pyyaml" }, { name = "virtualenv" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/40/f1/6d86a29246dfd2e9b6237f0b5823717f60cad94d47ddc26afa916d21f525/pre_commit-4.5.1.tar.gz", hash = "sha256:eb545fcff725875197837263e977ea257a402056661f09dae08e4b149b030a61", size = 198232 } +sdist = { url = "https://files.pythonhosted.org/packages/25/3a/ddb78f32a0814e66b18a099377a106a2dcdce92d86a034d69d65df9b256e/pre_commit-4.6.1.tar.gz", hash = "sha256:03e809865c7d178b9979d06c761fcbfe6808fdaded8581a745bb110e52050421", size = 198646, upload-time = "2026-07-21T20:56:58.225Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/5d/19/fd3ef348460c80af7bb4669ea7926651d1f95c23ff2df18b9d24bab4f3fa/pre_commit-4.5.1-py2.py3-none-any.whl", hash = "sha256:3b3afd891e97337708c1674210f8eba659b52a38ea5f822ff142d10786221f77", size = 226437 }, + { url = "https://files.pythonhosted.org/packages/fb/49/bc925106abcdac498074f2cbe6137e94e09f418dd2b7775df5b577dc0313/pre_commit-4.6.1-py2.py3-none-any.whl", hash = "sha256:0e3b2942510d1fb34eec167a3ec57331bf8442122f1153a9fb8b58f5c49b2717", size = 226186, upload-time = "2026-07-21T20:56:57.064Z" }, ] [[package]] name = "protobuf" -version = "7.34.1" +version = "7.35.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/6b/6b/a0e95cad1ad7cc3f2c6821fcab91671bd5b78bd42afb357bb4765f29bc41/protobuf-7.34.1.tar.gz", hash = "sha256:9ce42245e704cc5027be797c1db1eb93184d44d1cdd71811fb2d9b25ad541280", size = 454708 } +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/ec/11/3325d41e6ee15bf1125654301211247b042563bcc898784351252549a8ad/protobuf-7.34.1-cp310-abi3-macosx_10_9_universal2.whl", hash = "sha256:d8b2cc79c4d8f62b293ad9b11ec3aebce9af481fa73e64556969f7345ebf9fc7", size = 429247 }, - { url = "https://files.pythonhosted.org/packages/eb/9d/aa69df2724ff63efa6f72307b483ce0827f4347cc6d6df24b59e26659fef/protobuf-7.34.1-cp310-abi3-manylinux2014_aarch64.whl", hash = "sha256:5185e0e948d07abe94bb76ec9b8416b604cfe5da6f871d67aad30cbf24c3110b", size = 325753 }, - { url = "https://files.pythonhosted.org/packages/92/e8/d174c91fd48e50101943f042b09af9029064810b734e4160bbe282fa1caa/protobuf-7.34.1-cp310-abi3-manylinux2014_s390x.whl", hash = "sha256:403b093a6e28a960372b44e5eb081775c9b056e816a8029c61231743d63f881a", size = 340198 }, - { url = "https://files.pythonhosted.org/packages/53/1b/3b431694a4dc6d37b9f653f0c64b0a0d9ec074ee810710c0c3da21d67ba7/protobuf-7.34.1-cp310-abi3-manylinux2014_x86_64.whl", hash = "sha256:8ff40ce8cd688f7265326b38d5a1bed9bfdf5e6723d49961432f83e21d5713e4", size = 324267 }, - { url = "https://files.pythonhosted.org/packages/85/29/64de04a0ac142fb685fd09999bc3d337943fb386f3a0ec57f92fd8203f97/protobuf-7.34.1-cp310-abi3-win32.whl", hash = "sha256:34b84ce27680df7cca9f231043ada0daa55d0c44a2ddfaa58ec1d0d89d8bf60a", size = 426628 }, - { url = "https://files.pythonhosted.org/packages/4d/87/cb5e585192a22b8bd457df5a2c16a75ea0db9674c3a0a39fc9347d84e075/protobuf-7.34.1-cp310-abi3-win_amd64.whl", hash = "sha256:e97b55646e6ce5cbb0954a8c28cd39a5869b59090dfaa7df4598a7fba869468c", size = 437901 }, - { url = "https://files.pythonhosted.org/packages/88/95/608f665226bca68b736b79e457fded9a2a38c4f4379a4a7614303d9db3bc/protobuf-7.34.1-py3-none-any.whl", hash = "sha256:bb3812cd53aefea2b028ef42bd780f5b96407247f20c6ef7c679807e9d188f11", size = 170715 }, + { 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 = "py-cpuinfo" version = "9.0.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/37/a8/d832f7293ebb21690860d2e01d8115e5ff6f2ae8bbdc953f0eb0fa4bd2c7/py-cpuinfo-9.0.0.tar.gz", hash = "sha256:3cdbbf3fac90dc6f118bfd64384f309edeadd902d7c8fb17f02ffa1fc3f49690", size = 104716 } +sdist = { url = "https://files.pythonhosted.org/packages/37/a8/d832f7293ebb21690860d2e01d8115e5ff6f2ae8bbdc953f0eb0fa4bd2c7/py-cpuinfo-9.0.0.tar.gz", hash = "sha256:3cdbbf3fac90dc6f118bfd64384f309edeadd902d7c8fb17f02ffa1fc3f49690", size = 104716, upload-time = "2022-10-25T20:38:06.303Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e0/a9/023730ba63db1e494a271cb018dcd361bd2c917ba7004c3e49d5daf795a2/py_cpuinfo-9.0.0-py3-none-any.whl", hash = "sha256:859625bc251f64e21f077d099d4162689c762b5d6a4c3c97553d56241c9674d5", size = 22335 }, + { url = "https://files.pythonhosted.org/packages/e0/a9/023730ba63db1e494a271cb018dcd361bd2c917ba7004c3e49d5daf795a2/py_cpuinfo-9.0.0-py3-none-any.whl", hash = "sha256:859625bc251f64e21f077d099d4162689c762b5d6a4c3c97553d56241c9674d5", size = 22335, upload-time = "2022-10-25T20:38:27.636Z" }, ] [[package]] name = "pycparser" version = "3.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/1b/7d/92392ff7815c21062bea51aa7b87d45576f649f16458d78b7cf94b9ab2e6/pycparser-3.0.tar.gz", hash = "sha256:600f49d217304a5902ac3c37e1281c9fe94e4d0489de643a9504c5cdfdfc6b29", size = 103492 } +sdist = { url = "https://files.pythonhosted.org/packages/1b/7d/92392ff7815c21062bea51aa7b87d45576f649f16458d78b7cf94b9ab2e6/pycparser-3.0.tar.gz", hash = "sha256:600f49d217304a5902ac3c37e1281c9fe94e4d0489de643a9504c5cdfdfc6b29", size = 103492, upload-time = "2026-01-21T14:26:51.89Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/0c/c3/44f3fbbfa403ea2a7c779186dc20772604442dde72947e7d01069cbe98e3/pycparser-3.0-py3-none-any.whl", hash = "sha256:b727414169a36b7d524c1c3e31839a521725078d7b2ff038656844266160a992", size = 48172 }, + { url = "https://files.pythonhosted.org/packages/0c/c3/44f3fbbfa403ea2a7c779186dc20772604442dde72947e7d01069cbe98e3/pycparser-3.0-py3-none-any.whl", hash = "sha256:b727414169a36b7d524c1c3e31839a521725078d7b2ff038656844266160a992", size = 48172, upload-time = "2026-01-21T14:26:50.693Z" }, ] [[package]] name = "pygments" version = "2.20.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/c3/b2/bc9c9196916376152d655522fdcebac55e66de6603a76a02bca1b6414f6c/pygments-2.20.0.tar.gz", hash = "sha256:6757cd03768053ff99f3039c1a36d6c0aa0b263438fcab17520b30a303a82b5f", size = 4955991 } +sdist = { url = "https://files.pythonhosted.org/packages/c3/b2/bc9c9196916376152d655522fdcebac55e66de6603a76a02bca1b6414f6c/pygments-2.20.0.tar.gz", hash = "sha256:6757cd03768053ff99f3039c1a36d6c0aa0b263438fcab17520b30a303a82b5f", size = 4955991, upload-time = "2026-03-29T13:29:33.898Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/f4/7e/a72dd26f3b0f4f2bf1dd8923c85f7ceb43172af56d63c7383eb62b332364/pygments-2.20.0-py3-none-any.whl", hash = "sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176", size = 1231151 }, + { url = "https://files.pythonhosted.org/packages/f4/7e/a72dd26f3b0f4f2bf1dd8923c85f7ceb43172af56d63c7383eb62b332364/pygments-2.20.0-py3-none-any.whl", hash = "sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176", size = 1231151, upload-time = "2026-03-29T13:29:30.038Z" }, ] [[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 } +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 }, + { 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 = "pytest" -version = "9.0.3" +version = "9.1.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama", marker = "sys_platform == 'win32'" }, @@ -1585,9 +1951,9 @@ dependencies = [ { name = "pluggy" }, { name = "pygments" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/7d/0d/549bd94f1a0a402dc8cf64563a117c0f3765662e2e668477624baeec44d5/pytest-9.0.3.tar.gz", hash = "sha256:b86ada508af81d19edeb213c681b1d48246c1a91d304c6c81a427674c17eb91c", size = 1572165 } +sdist = { url = "https://files.pythonhosted.org/packages/e4/47/b9efed96c114afcfa3c9d3fe98a76a1d14c74a9e266d397cf6eb64be5e01/pytest-9.1.1.tar.gz", hash = "sha256:1088fbde8f2b49d95a549a195707afa7a76a3ce9bcadc26b6d71f0ffda5fe313", size = 1636369, upload-time = "2026-06-19T10:58:32.857Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d4/24/a372aaf5c9b7208e7112038812994107bc65a84cd00e0354a88c2c77a617/pytest-9.0.3-py3-none-any.whl", hash = "sha256:2c5efc453d45394fdd706ade797c0a81091eccd1d6e4bccfcd476e2b8e0ab5d9", size = 375249 }, + { url = "https://files.pythonhosted.org/packages/24/25/1de2678b631f5a49215c6c96fff41ba892b0a34df68d6d80292b1b48aa7f/pytest-9.1.1-py3-none-any.whl", hash = "sha256:37a86b45efb9a47a61a36449063e8e18d0cab3161329fc099eb21783169c4f0c", size = 386536, upload-time = "2026-06-19T10:58:31.347Z" }, ] [[package]] @@ -1597,32 +1963,36 @@ 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 } +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 }, + { 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 = "python-discovery" -version = "1.2.2" +version = "1.5.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "filelock" }, { name = "platformdirs" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/de/ef/3bae0e537cfe91e8431efcba4434463d2c5a65f5a89edd47c6cf2f03c55f/python_discovery-1.2.2.tar.gz", hash = "sha256:876e9c57139eb757cb5878cbdd9ae5379e5d96266c99ef731119e04fffe533bb", size = 58872 } +sdist = { url = "https://files.pythonhosted.org/packages/f1/51/276f964496a5714ab9f320896195639086881c2b39c03b5ad13de84acbb8/python_discovery-1.5.0.tar.gz", hash = "sha256:3e014c6327154d3dda27939a9a0dc9c5c000439f1906d3f303b48f984bd2ecef", size = 72483, upload-time = "2026-07-21T13:14:14.641Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d8/db/795879cc3ddfe338599bddea6388cc5100b088db0a4caf6e6c1af1c27e04/python_discovery-1.2.2-py3-none-any.whl", hash = "sha256:e1ae95d9af875e78f15e19aed0c6137ab1bb49c200f21f5061786490c9585c7a", size = 31894 }, + { url = "https://files.pythonhosted.org/packages/c7/7b/14882602ddee241d7984a742fcb423cb4a30fb0d6efc546ac3129fba475a/python_discovery-1.5.0-py3-none-any.whl", hash = "sha256:70c4fc61b4e7404e44f01d6fc44a715c4d685ca6cea83d295922f05891877c98", size = 34205, upload-time = "2026-07-21T13:14:13.398Z" }, ] [[package]] name = "pyturbojpeg" -version = "1.8.3" +version = "2.5.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "numpy" }, + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, + { name = "numpy", version = "2.5.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/55/fe/b525bca5e85688a283839126095d3e7e6d9bb5e7f23c68e57ad30f43af14/pyturbojpeg-2.5.0.tar.gz", hash = "sha256:572e74886110e0bd85f8a95a188f1cda94c4a5f0222ff38a22d7e12faeb9844b", size = 49265, upload-time = "2026-07-14T16:00:50.511Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6c/e4/b19be937c95df9a02d6337178088b56fe77c2656eab46489344c7ac510e9/pyturbojpeg-2.5.0-py3-none-any.whl", hash = "sha256:2c10c2de86aa0e4fd9d08de187e46e975d108db35c25842d342393913cf54c36", size = 27455, upload-time = "2026-07-14T16:00:49.05Z" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/f2/2b/5fc7a7f51af947708a5d75d7637e923d2d4e60f43f6a4cfe55ae1ea241a2/pyturbojpeg-1.8.3.tar.gz", hash = "sha256:c131591a3990cc57f45a8b2705d6261c25df913a19b1fe88de5e911dbe04a1d4", size = 12757 } [[package]] name = "pyvips" @@ -1631,87 +2001,101 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "cffi" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/2d/6a/282936de9faac6addf6bc8792c18e006489d0023ffd8856b8643f54d0558/pyvips-3.1.1.tar.gz", hash = "sha256:84fe744d023b1084ac2516bb17064cacd41c7f8aabf8e524dd383534941b9301", size = 56951 } +sdist = { url = "https://files.pythonhosted.org/packages/2d/6a/282936de9faac6addf6bc8792c18e006489d0023ffd8856b8643f54d0558/pyvips-3.1.1.tar.gz", hash = "sha256:84fe744d023b1084ac2516bb17064cacd41c7f8aabf8e524dd383534941b9301", size = 56951, upload-time = "2025-12-09T18:38:06.355Z" } [[package]] name = "pyvips-binary" -version = "8.18.2" +version = "8.18.4" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "cffi" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/1c/55/38f745761aeb1726a9bb02ca9d05a1751e0acf5953d06631386e44718333/pyvips_binary-8.18.2.tar.gz", hash = "sha256:603d64f35390b10945dbe4b8184f513de48af6e074f21f077c14d723d43472c4", size = 3822 } +sdist = { url = "https://files.pythonhosted.org/packages/0b/a2/78646a4c58dde4e51c883c1b2fb3c157b385782092173baf3f91b48666a8/pyvips_binary-8.18.4.tar.gz", hash = "sha256:fc4c10970da40b8c16e556f2022a9e4600f74d7ac6ea578a2c16efe03aa3fba1", size = 3792, upload-time = "2026-07-06T10:36:22.797Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ab/b0/e7e3f00d1908007769007da306c51c4507448dabcc95025a530ec017958a/pyvips_binary-8.18.2-cp37-abi3-macosx_10_15_x86_64.whl", hash = "sha256:c195ef12834ffa0a266f0c56f4058dcdee510b9a34e1d91b6eea4f4ee28a5fab", size = 8479277 }, - { url = "https://files.pythonhosted.org/packages/17/ad/30937ce6006c8cfc7209a4040201803570495c97339a8412d863cc83f13a/pyvips_binary-8.18.2-cp37-abi3-macosx_11_0_arm64.whl", hash = "sha256:9bc45eb1a37076ca88a11df069dae066b7d9f22e01105bedb7cacf9883ec583f", size = 7585020 }, - { url = "https://files.pythonhosted.org/packages/41/28/21063b9be92f9cae68f991bab0e0806fa519b378dbd1a12344d3025819c9/pyvips_binary-8.18.2-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:bfd329f1e53d5e32525ed1478ddf308b78b0105696e25aed0609a74394536b89", size = 7818567 }, - { url = "https://files.pythonhosted.org/packages/13/48/e9b6a816cde78f0d21d0506f25ba3c287998c8566ae6fe99ea5944122e5e/pyvips_binary-8.18.2-cp37-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:c4f9b066ed4ddfd326c2e113542a865475075d447fd4095dd47edc64fbe0dba5", size = 7535206 }, - { url = "https://files.pythonhosted.org/packages/44/7f/a975168aa59fe133617c31312f344ca5e6846c413bc1e48000c082e25586/pyvips_binary-8.18.2-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:eca17299dd1f5593922d52f88da3d78097faac0d27f4598a976d5dd9bc0eb9d3", size = 7763525 }, - { url = "https://files.pythonhosted.org/packages/9f/2f/22c18b200ab6bf6bea0e31f825daf496e60ef787b3663eb098302e7f5323/pyvips_binary-8.18.2-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:691941c6e384f582a72cabaebe49efefcd4eb5793d4dd1e06113f5899dbfa50b", size = 7878137 }, - { url = "https://files.pythonhosted.org/packages/6b/53/f5969e402b0094a530633294281334f5ed494d0856b71bdf1ac2000450e5/pyvips_binary-8.18.2-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:10b422558db573375a899ba39a502ba055dfc87c64fb472f2bac0ec3e04a78a9", size = 7785097 }, - { url = "https://files.pythonhosted.org/packages/1e/28/f98613a2de0c7987e3218b0a5819e6156569e116cd15c5d5d514c9d70c44/pyvips_binary-8.18.2-cp37-abi3-win32.whl", hash = "sha256:e8cb606a357c1a9dc8248e88f27155bb3010964819ccf282dd59d240473a67d5", size = 8395704 }, - { url = "https://files.pythonhosted.org/packages/88/31/47b912c443a6b9cefbccd409566250e0f0a5667abe3db8b700d95ad45c44/pyvips_binary-8.18.2-cp37-abi3-win_amd64.whl", hash = "sha256:985342e39d5fab6d5192f734bc64f3edc95bca0847c568896d1dade0fe492ee6", size = 8343719 }, - { url = "https://files.pythonhosted.org/packages/20/80/e8982a521e6c0c567264c14975052c21908d6588c8b35f7a847ecb318f8a/pyvips_binary-8.18.2-cp37-abi3-win_arm64.whl", hash = "sha256:f8c373285e586aab67020505786daf43e76d755c88f62bfca4c350a2c6f352ec", size = 7537820 }, + { url = "https://files.pythonhosted.org/packages/c7/2a/137d2bde18772a35f01989f47502b3b514c3d86fcc1f6053107e9644af86/pyvips_binary-8.18.4-cp37-abi3-macosx_10_15_x86_64.whl", hash = "sha256:0a353d804db997ab6e2f709af5885e5b5fbd36bfae3b83bce6419fdc0ec67a0b", size = 8804591, upload-time = "2026-07-06T10:35:59.489Z" }, + { url = "https://files.pythonhosted.org/packages/ad/53/c1d9e0e0aca60c1de920d07af8266b8b87224eb9f19a89629b9920485ce9/pyvips_binary-8.18.4-cp37-abi3-macosx_11_0_arm64.whl", hash = "sha256:06c1bca5c4e1d41f4305cfd02d53574bde102585b8135f90da8ea6fb54d8ef54", size = 7932856, upload-time = "2026-07-06T10:36:02.151Z" }, + { url = "https://files.pythonhosted.org/packages/2c/47/d5af1d78a0f995755d2d1ebb3a9e1f3d5252a1e1cec4518d6c76b07d510a/pyvips_binary-8.18.4-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:81ed9b4d281a7132c498f62564429a39b1953398597867cd576263aef9bef272", size = 8120180, upload-time = "2026-07-06T10:36:04.382Z" }, + { url = "https://files.pythonhosted.org/packages/aa/70/d0fdcc6b4be49f96f5a378510a3e251a591b84fc99d00779b74f3d1d4062/pyvips_binary-8.18.4-cp37-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:b5fe5014555e38ff796eac3513a49b3d832999ba65c25999038c1fa861f7b88c", size = 7821453, upload-time = "2026-07-06T10:36:06.823Z" }, + { url = "https://files.pythonhosted.org/packages/9d/ff/08ad76bd3974791b6116026bc9e9347795d2d23f283c6fa53c208dbd04d6/pyvips_binary-8.18.4-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:68a24313cd76c9213e1208887aa1dabaa502e2be3f180222628191478977e2d0", size = 8004640, upload-time = "2026-07-06T10:36:09.489Z" }, + { url = "https://files.pythonhosted.org/packages/57/f7/6189051dd688a45d922fd54cb380a3b4e18e1ae3cad1103966b38c74a5c5/pyvips_binary-8.18.4-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:d2808f3395e52b5a289111d57f1dba0fcd2c81573eda4cef768e7633ce446037", size = 8134850, upload-time = "2026-07-06T10:36:12.101Z" }, + { url = "https://files.pythonhosted.org/packages/06/ba/1e565f43d0d734901ec3fdb6c2df5985be820e2702942c37fdfb94e17db4/pyvips_binary-8.18.4-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:b30028e10d3feaf14067c8658b827d0471464ba838c0795d3bf201f151800a1a", size = 8039192, upload-time = "2026-07-06T10:36:14.292Z" }, + { url = "https://files.pythonhosted.org/packages/bd/5e/01fe55141a2e77639429f004e678c19e468bd2cfdd7c66f62e487d0dee5b/pyvips_binary-8.18.4-cp37-abi3-win32.whl", hash = "sha256:5e49ab5164efca9cc6230f12237130f902bc2639b765342ee3c16eb1ed15b265", size = 7968523, upload-time = "2026-07-06T10:36:17.152Z" }, + { url = "https://files.pythonhosted.org/packages/c2/6e/5248336741c0a416d16e0f56efecaa9c1514005e58665148cd6d841ea7ef/pyvips_binary-8.18.4-cp37-abi3-win_amd64.whl", hash = "sha256:8f89588e55c2824fb8cbdda94baffb81da6b87fce08b517ccfdd233aae0ee418", size = 7930802, upload-time = "2026-07-06T10:36:19.346Z" }, + { url = "https://files.pythonhosted.org/packages/16/80/c3f35ef439fcdbc7fba055fd31f71ef4f6f516e3a75c4d39fc80803329be/pyvips_binary-8.18.4-cp37-abi3-win_arm64.whl", hash = "sha256:c98a26842cd08c238405d3f83b04d3de11c35ca24d0e1a9d0f04f3f1fdf20d8c", size = 7215735, upload-time = "2026-07-06T10:36:21.396Z" }, ] [[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 } -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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { url = "https://files.pythonhosted.org/packages/45/91/47a6e1c42d9ee337c4839208f30d9f09caa9f720ec7582917b264defc875/pyyaml-6.0.3-cp311-cp311-win32.whl", hash = "sha256:8098f252adfa6c80ab48096053f512f2321f0b998f98150cea9bd23d83e1467b", size = 142543 }, - { url = "https://files.pythonhosted.org/packages/da/e3/ea007450a105ae919a72393cb06f122f288ef60bba2dc64b26e2646fa315/pyyaml-6.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:9f3bfb4965eb874431221a3ff3fdcddc7e74e3b07799e0e84ca4a0f867d449bf", size = 158763 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { url = "https://files.pythonhosted.org/packages/74/93/7baea19427dcfbe1e5a372d81473250b379f04b1bd3c4c5ff825e2327202/pyyaml-6.0.3-cp312-cp312-win32.whl", hash = "sha256:96b533f0e99f6579b3d4d4995707cf36df9100d67e0c8303a0c55b27b5f99bc5", size = 137658 }, - { url = "https://files.pythonhosted.org/packages/86/bf/899e81e4cce32febab4fb42bb97dcdf66bc135272882d1987881a4b519e9/pyyaml-6.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:5fcd34e47f6e0b794d17de1b4ff496c00986e1c83f7ab2fb8fcfe9616ff7477b", size = 154003 }, - { url = "https://files.pythonhosted.org/packages/1a/08/67bd04656199bbb51dbed1439b7f27601dfb576fb864099c7ef0c3e55531/pyyaml-6.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:64386e5e707d03a7e172c0701abfb7e10f0fb753ee1d773128192742712a98fd", size = 140344 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { url = "https://files.pythonhosted.org/packages/de/94/980b50a6531b3019e45ddeada0626d45fa85cbe22300844a7983285bed3b/pyyaml-6.0.3-cp313-cp313-win32.whl", hash = "sha256:d0eae10f8159e8fdad514efdc92d74fd8d682c933a6dd088030f3834bc8e6b26", size = 137427 }, - { url = "https://files.pythonhosted.org/packages/97/c9/39d5b874e8b28845e4ec2202b5da735d0199dbe5b8fb85f91398814a9a46/pyyaml-6.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:79005a0d97d5ddabfeeea4cf676af11e647e41d81c9a7722a193022accdb6b7c", size = 154090 }, - { url = "https://files.pythonhosted.org/packages/73/e8/2bdf3ca2090f68bb3d75b44da7bbc71843b19c9f2b9cb9b0f4ab7a5a4329/pyyaml-6.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:5498cd1645aa724a7c71c8f378eb29ebe23da2fc0d7a08071d89469bf1d2defb", size = 140246 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { url = "https://files.pythonhosted.org/packages/23/20/bb6982b26a40bb43951265ba29d4c246ef0ff59c9fdcdf0ed04e0687de4d/pyyaml-6.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:4a2e8cebe2ff6ab7d1050ecd59c25d4c8bd7e6f400f5f82b96557ac0abafd0ac", size = 156429 }, - { url = "https://files.pythonhosted.org/packages/f4/f4/a4541072bb9422c8a883ab55255f918fa378ecf083f5b85e87fc2b4eda1b/pyyaml-6.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:93dda82c9c22deb0a405ea4dc5f2d0cda384168e466364dec6255b293923b2f3", size = 143912 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { 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 }, - { url = "https://files.pythonhosted.org/packages/f0/7a/1c7270340330e575b92f397352af856a8c06f230aa3e76f86b39d01b416a/pyyaml-6.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4ad1906908f2f5ae4e5a8ddfce73c320c2a1429ec52eafd27138b7f1cbe341c9", size = 174062 }, - { url = "https://files.pythonhosted.org/packages/f1/12/de94a39c2ef588c7e6455cfbe7343d3b2dc9d6b6b2f40c4c6565744c873d/pyyaml-6.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b", size = 149341 }, +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 = "referencing" +version = "0.37.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "attrs" }, + { name = "rpds-py" }, + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/22/f5/df4e9027acead3ecc63e50fe1e36aca1523e1719559c499951bb4b53188f/referencing-0.37.0.tar.gz", hash = "sha256:44aefc3142c5b842538163acb373e24cce6632bd54bdb01b21ad5863489f50d8", size = 78036, upload-time = "2025-10-13T15:30:48.871Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2c/58/ca301544e1fa93ed4f80d724bf5b194f6e4b945841c5bfd555878eea9fcb/referencing-0.37.0-py3-none-any.whl", hash = "sha256:381329a9f99628c9069361716891d34ad94af76e461dcb0335825aecc7692231", size = 26766, upload-time = "2025-10-13T15:30:47.625Z" }, ] [[package]] name = "requests" -version = "2.33.1" +version = "2.34.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "certifi" }, @@ -1719,9 +2103,9 @@ dependencies = [ { name = "idna" }, { name = "urllib3" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/5f/a4/98b9c7c6428a668bf7e42ebb7c79d576a1c3c1e3ae2d47e674b468388871/requests-2.33.1.tar.gz", hash = "sha256:18817f8c57c6263968bc123d237e3b8b08ac046f5456bd1e307ee8f4250d3517", size = 134120 } +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/d7/8e/7540e8a2036f79a125c1d2ebadf69ed7901608859186c856fa0388ef4197/requests-2.33.1-py3-none-any.whl", hash = "sha256:4e6d1ef462f3626a1f0a0a9c42dd93c63bad33f9f1c1937509b8c5c8718ab56a", size = 64947 }, + { 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]] @@ -1732,9 +2116,132 @@ dependencies = [ { name = "markdown-it-py" }, { name = "pygments" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/c0/8f/0722ca900cc807c13a6a0c696dacf35430f72e0ec571c4275d2371fca3e9/rich-15.0.0.tar.gz", hash = "sha256:edd07a4824c6b40189fb7ac9bc4c52536e9780fbbfbddf6f1e2502c31b068c36", size = 230680 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/82/3b/64d4899d73f91ba49a8c18a8ff3f0ea8f1c1d75481760df8c68ef5235bf5/rich-15.0.0-py3-none-any.whl", hash = "sha256:33bd4ef74232fb73fe9279a257718407f169c09b78a87ad3d296f548e27de0bb", size = 310654 }, +sdist = { url = "https://files.pythonhosted.org/packages/c0/8f/0722ca900cc807c13a6a0c696dacf35430f72e0ec571c4275d2371fca3e9/rich-15.0.0.tar.gz", hash = "sha256:edd07a4824c6b40189fb7ac9bc4c52536e9780fbbfbddf6f1e2502c31b068c36", size = 230680, upload-time = "2026-04-12T08:24:00.75Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/82/3b/64d4899d73f91ba49a8c18a8ff3f0ea8f1c1d75481760df8c68ef5235bf5/rich-15.0.0-py3-none-any.whl", hash = "sha256:33bd4ef74232fb73fe9279a257718407f169c09b78a87ad3d296f548e27de0bb", size = 310654, upload-time = "2026-04-12T08:24:02.83Z" }, +] + +[[package]] +name = "rpds-py" +version = "2026.6.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/aa/2a/9618a122aeb2a169a28b03889a2995fe297588964333d4a7d67bdf46e147/rpds_py-2026.6.3.tar.gz", hash = "sha256:1cebd1337c242e4ec2293e541f712b2da849b29f48f0c293684b71c0632625d4", size = 64051, upload-time = "2026-06-30T07:17:53.009Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/94/1f/a2dca5ffdbf1d475ffc4e80e4d5d720ff3a00f691795910116960ee12511/rpds_py-2026.6.3-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:7b689145a1485c335569bd056464f3243a29af7ed3871c7be31ad624ba239bc7", size = 342174, upload-time = "2026-06-30T07:14:54.821Z" }, + { url = "https://files.pythonhosted.org/packages/4d/dc/323d08583c0832911768663d1944f0107fcd4088704858d84b5e06d105a0/rpds_py-2026.6.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:db08f45aecde626498fb3df07bcf6d2ec040af42e859a4f5040d79c200342911", size = 345513, upload-time = "2026-06-30T07:14:56.515Z" }, + { url = "https://files.pythonhosted.org/packages/0b/2a/e31989834d18d2f26ec1d2774c5b1eb3331df4ea8ada525175294c94b48a/rpds_py-2026.6.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:acc992ab27b15f852c76755eb2ab7dce86585ddadba6fa5946e58556088845b4", size = 373783, upload-time = "2026-06-30T07:14:57.736Z" }, + { url = "https://files.pythonhosted.org/packages/87/fe/e80107ee3639585c9941c17d6a42cd65325022f656c023191fce78c324c8/rpds_py-2026.6.3-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7f88d653e7b3b779d71ae7454e20dcc9b6bae903f33c269db9f2be41bda3f261", size = 378316, upload-time = "2026-06-30T07:14:59.077Z" }, + { url = "https://files.pythonhosted.org/packages/22/6f/81e3adf81acfb6fa694de2a6e4e7d8863121e3e0799e0a7725e6cf5679c4/rpds_py-2026.6.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e52655eaf81e32593abedaa4bfe33170c8cfedf3365ed9be6e11e07f148f0278", size = 499423, upload-time = "2026-06-30T07:15:00.488Z" }, + { url = "https://files.pythonhosted.org/packages/2d/9a/41263969df0ce3d9af2a96d5005a288200af1989aed3354bfceb5fc0b21f/rpds_py-2026.6.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dfcc8b909769d19db55c7cc9541eb64b9b774b1057ffffb4f1048070475bb9f9", size = 386077, upload-time = "2026-06-30T07:15:01.911Z" }, + { url = "https://files.pythonhosted.org/packages/5e/19/7e98f468bd50346faff5b10e5297374b443bfdddacc8e9fbc65984539597/rpds_py-2026.6.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9c1255b302953c86a486b81d330d5ee1d5bd937691ce271b6be0ef0e299eaab7", size = 371315, upload-time = "2026-06-30T07:15:03.317Z" }, + { url = "https://files.pythonhosted.org/packages/99/3c/2b973b4d371906a134b03decfea7f5d9835a2c6d263454392e15b64b5b18/rpds_py-2026.6.3-cp311-cp311-manylinux_2_31_riscv64.whl", hash = "sha256:8d2294a31386bfa251d8c8a39472beee17db67d4f1a6eabea665d35c9a4461c3", size = 383502, upload-time = "2026-06-30T07:15:04.627Z" }, + { url = "https://files.pythonhosted.org/packages/98/2a/12e2799500af0a307bca76b63361c51f9fe479223561489c29eea1f2ee41/rpds_py-2026.6.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f8f23ead891a3b762f35ab3b04623da7056545b48aa60d59957e6789914545da", size = 402673, upload-time = "2026-06-30T07:15:05.856Z" }, + { url = "https://files.pythonhosted.org/packages/2d/e3/21e5872d165fe08be4f229e3d5ee9d90019c0bf0e5538de60dbd54009450/rpds_py-2026.6.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:421aba32367055614287a4292b6a17f1939c9452299f7a0209c117e990b646d4", size = 549964, upload-time = "2026-06-30T07:15:07.159Z" }, + { url = "https://files.pythonhosted.org/packages/1a/d0/5ee0fe36844297de8123bee27bc12078c1a7416ad9f1b8a8ca18d6b0c0ac/rpds_py-2026.6.3-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:1e5822dfc2f0d4ab7e745eaa6d85945069329beeccef965af3f3bb26058fcab6", size = 615446, upload-time = "2026-06-30T07:15:08.531Z" }, + { url = "https://files.pythonhosted.org/packages/b1/80/1ea5873cb683f2fbe5f21b23ea1f6d179ead19f3c5b249b7eb5dca568ef2/rpds_py-2026.6.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:83e35b57523816c8613fd0776b40cd8bb9f596b37ddd2692eb4a6bb5ab2f8c93", size = 576975, upload-time = "2026-06-30T07:15:09.97Z" }, + { url = "https://files.pythonhosted.org/packages/c9/e1/90ef639217a5ddb15b7f4f61b1c33911fd044ad03c311bafdd2bcab85582/rpds_py-2026.6.3-cp311-cp311-win32.whl", hash = "sha256:de3eceba0b683bcbb1ab93da016d0270df1f9ae7be716b40214c5dafac6ea45a", size = 204453, upload-time = "2026-06-30T07:15:11.324Z" }, + { url = "https://files.pythonhosted.org/packages/f2/b7/b7a1695d7af36f521fb11e80d6d3adbd744f73b921859bd3c2a2c0dc706f/rpds_py-2026.6.3-cp311-cp311-win_amd64.whl", hash = "sha256:2c54a076ca4d370980ab57bc0e31df57bbe8d41340436a90ef8b1219a3cbb127", size = 223219, upload-time = "2026-06-30T07:15:12.476Z" }, + { url = "https://files.pythonhosted.org/packages/d7/a2/145afacf796e4506062825941176ad9445c2dcf2b3b6a1f13d3030a15e19/rpds_py-2026.6.3-cp311-cp311-win_arm64.whl", hash = "sha256:168c733a7112e071bb7a66460e667edfcff06c017a3c523f7a8a8e08d0140804", size = 219137, upload-time = "2026-06-30T07:15:13.631Z" }, + { url = "https://files.pythonhosted.org/packages/5c/be/2e8974163072e7bab7df1a5acd54c4498e75e35d6d18b864d3a9d5dadc92/rpds_py-2026.6.3-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:a0811d33247c3d6128a3001d763f2aa056bb3425204335400ac54f89eec3a0d0", size = 343691, upload-time = "2026-06-30T07:15:14.96Z" }, + { url = "https://files.pythonhosted.org/packages/a4/73/319dfa745dd668efe89309141ded489126461fcecd2b8f3a3cda185129b6/rpds_py-2026.6.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:538949e262e46caa31ac01bdb3c1e8f642622922cacbabbae6a8445d9dc33eaf", size = 338542, upload-time = "2026-06-30T07:15:16.267Z" }, + { url = "https://files.pythonhosted.org/packages/21/63/4239893be1c4d09b709b1a8f6be4188f0870084ff547f46606b8a75f1b03/rpds_py-2026.6.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:55927d532399c2c646100ff7feb48eaa940ad70f42cd68e1328f3ded9f81ca24", size = 368180, upload-time = "2026-06-30T07:15:17.62Z" }, + { url = "https://files.pythonhosted.org/packages/1c/ca/9c5de382225234ceb37b1844ebdb140db12b2a278bb9efe2fcd19f6c82ce/rpds_py-2026.6.3-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f56f1695bc5c0871cbc33dc0130fcf503aab0c57dcc5a6700a4f49eba4f2652e", size = 375067, upload-time = "2026-06-30T07:15:18.952Z" }, + { url = "https://files.pythonhosted.org/packages/87/dc/863f69d1bf04ade34b7fe0d59b9fdf6f0135fe2d7cbca74f1d665589559d/rpds_py-2026.6.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:270b293dae9058fc9fcedab50f13cebf46fb8ed1d1d54e0521a9da5d6b211975", size = 490509, upload-time = "2026-06-30T07:15:20.434Z" }, + { url = "https://files.pythonhosted.org/packages/ce/ef/eac16a12048b45ec7c7fa94f2be3438a5f26bf9cc8580b18a1cfd609b7f6/rpds_py-2026.6.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:127565fead0a10943b282957bd5447804ff3160ad79f2ad2635e6d249e380680", size = 382754, upload-time = "2026-06-30T07:15:21.831Z" }, + { url = "https://files.pythonhosted.org/packages/04/8f/d2f3f532616be4d06c316ef119683e832bd3d41e112bf3a88f4151c95b17/rpds_py-2026.6.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ecabd69db66de867690f9797f2f8fa27ba501bbc24540cbdbdc649cd15888ba6", size = 366189, upload-time = "2026-06-30T07:15:23.371Z" }, + { url = "https://files.pythonhosted.org/packages/e3/29/41a7b0e98a4b44cd676ab7598419623373eb43b20be68c084935c1a8cf88/rpds_py-2026.6.3-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:58eadac9cd119677b60e1cf8ac4052f35949d71b8a9e5556efccbe82533cf22a", size = 377750, upload-time = "2026-06-30T07:15:24.659Z" }, + { url = "https://files.pythonhosted.org/packages/2e/05/ecda0bec46f9a1565090bcdc941d023f6a25aff85fda28f89f8d19878152/rpds_py-2026.6.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:7491ee23305ac3eb59e492b6945881f5cd77a6f731061a3f25b77fd40f9e99a4", size = 395576, upload-time = "2026-06-30T07:15:25.987Z" }, + { url = "https://files.pythonhosted.org/packages/68/a8/6ed52f03ee6cb854ce78785cc9a9a672eb880e83fd7224d471f667d151f1/rpds_py-2026.6.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2c99f7e8ccb3dd6e3e4bfeac657a7b208c9bac8075f4b078c02d7404c34107fa", size = 543807, upload-time = "2026-06-30T07:15:27.356Z" }, + { url = "https://files.pythonhosted.org/packages/8f/d6/156c0d3eea27ba09b92562ba2364ba124c0a061b199e17eac637cd25a5e2/rpds_py-2026.6.3-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:62698275682bf121181861295c9181e789030a2d516071f5b8f3c23c170cd0fc", size = 611187, upload-time = "2026-06-30T07:15:28.931Z" }, + { url = "https://files.pythonhosted.org/packages/f1/31/774212ed989c62f7f310220089f9b0a3fb8f40f5443d1727abd5d9f52bc9/rpds_py-2026.6.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a214c993455f99a89aaeadc9b21241900037adc9d97203e374d75513c5911822", size = 573030, upload-time = "2026-06-30T07:15:30.553Z" }, + { url = "https://files.pythonhosted.org/packages/c9/50/22f73127a41f1ce4f87fe39aadfb9a126345801c274aa93ae88456249327/rpds_py-2026.6.3-cp312-cp312-win32.whl", hash = "sha256:501f9f04a588d6a09179368c57071301445191767c64e4b52a6aa9871f1ef5ed", size = 202185, upload-time = "2026-06-30T07:15:32.027Z" }, + { url = "https://files.pythonhosted.org/packages/04/3a/f0ee4d4dde9d3b69dedf1b5f74e7a40017046d55052d173e418c6a94f960/rpds_py-2026.6.3-cp312-cp312-win_amd64.whl", hash = "sha256:2c958bf94822e9290a40aaf2a822d4bc5c88099093e3948ad6c571eca9272e5f", size = 220394, upload-time = "2026-06-30T07:15:33.359Z" }, + { url = "https://files.pythonhosted.org/packages/f3/83/3382fe37f809b59f02aac04dbc4e765b480b46ee0227ed516e3bdc4d3dfc/rpds_py-2026.6.3-cp312-cp312-win_arm64.whl", hash = "sha256:22bffe6042b9bcb0822bcd1955ec00e245daf17b4344e4ed8e9551b976b63e96", size = 215753, upload-time = "2026-06-30T07:15:34.778Z" }, + { url = "https://files.pythonhosted.org/packages/a4/9e/b818ee580026ec578138e961027a68820c40afeb1ec8f6819b54fb99e196/rpds_py-2026.6.3-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:3cfe765c1da0072636ca06628261e0ea05688e160d5c8a03e0217c3854037223", size = 343012, upload-time = "2026-06-30T07:15:36.005Z" }, + { url = "https://files.pythonhosted.org/packages/f3/6b/686d9dc4359a8f163cfbbf89ee0b4e586431de22fe8248edb63a8cf50d49/rpds_py-2026.6.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f4d78253f6996be4901669ad25319f842f740eccf4d58e3c7f3dd39e6dde1d8f", size = 338203, upload-time = "2026-06-30T07:15:37.462Z" }, + { url = "https://files.pythonhosted.org/packages/9e/9b/069aa329940f8207615e091f5eedbbd40e1e15eac68a0790fd05ccdf796c/rpds_py-2026.6.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:54f45a148e28767bf343d33a684693c70e451c6f4c0e9904709a723fafbdfc1f", size = 367984, upload-time = "2026-06-30T07:15:39.008Z" }, + { url = "https://files.pythonhosted.org/packages/14/db/34c203e4becff3703e4d3bc121842c00b8689197f398161203a880052f4e/rpds_py-2026.6.3-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:842e7b070435622248c7a2c44ae53fa1440e073cc3023bc919fed570884097a7", size = 374815, upload-time = "2026-06-30T07:15:40.253Z" }, + { url = "https://files.pythonhosted.org/packages/ee/7d/8071067d2cc453d916ad836e828c943f575e8a44612537759002a1e07381/rpds_py-2026.6.3-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8020133a74bd81b4572dd8e4be028a6b1ebcd70e6726edc3918008c08bee6ee6", size = 490545, upload-time = "2026-06-30T07:15:41.729Z" }, + { url = "https://files.pythonhosted.org/packages/a3/42/da06c5aa8f0484ff07f270787434204d9f4535e2f8c3b51ed402267e63c3/rpds_py-2026.6.3-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cdc7e35386f3847df728fbcb5e887e2d79c19e2fa1eba9e51b6621d23e3243af", size = 382828, upload-time = "2026-06-30T07:15:43.327Z" }, + { url = "https://files.pythonhosted.org/packages/57/d7/fe978efc2ae50abe48eb7464668ea99f53c010c60aeebb7b35ad27f23661/rpds_py-2026.6.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:acac386b453c2516111b50985d60ce46e7fadb5ea71ae7b25f4c946935bf27cf", size = 365678, upload-time = "2026-06-30T07:15:44.992Z" }, + { url = "https://files.pythonhosted.org/packages/69/9d/1d8922e1990b2a6eb532b6ff53d3e73d2b3bbffc84116c75826bee73dfc6/rpds_py-2026.6.3-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:425560c6fa0415f27261727bb20bd097568485e5eb0c121f1949417d1c516885", size = 377811, upload-time = "2026-06-30T07:15:46.523Z" }, + { url = "https://files.pythonhosted.org/packages/b1/3d/198dceafb4fb034a6a47347e1b0735d34e0bd4a50be4e898d408ee66cb14/rpds_py-2026.6.3-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a550fb4950a06dde3beb4721f5ad4b25bf4513784665b0a8522c792e2bd822a4", size = 395382, upload-time = "2026-06-30T07:15:47.955Z" }, + { url = "https://files.pythonhosted.org/packages/1f/f1/13968e49655d40b6b19d8b9140296bbc6f1d86b3f0f6c346cf9f1adddf4b/rpds_py-2026.6.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4f4bca01b63096f606e095734dd56e74e175f94cfbf24ff3d63281cec61f7bb7", size = 543832, upload-time = "2026-06-30T07:15:49.33Z" }, + { url = "https://files.pythonhosted.org/packages/ac/ab/289bcb1b90bd3e40a2900c561fa0e2087345ecbb094f0b870f2345142b7c/rpds_py-2026.6.3-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ccffae9a092a00deb7efd545fe5e2c33c33b88e7c054337e9a74c179347d0b7d", size = 611011, upload-time = "2026-06-30T07:15:50.847Z" }, + { url = "https://files.pythonhosted.org/packages/1e/16/5043105e679436ccfbc8e5e0dd2d663ed18a8b8113515fd06a5e5d77c83e/rpds_py-2026.6.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:1cf01971c4f2c5553b772a542e4aaf191789cd331bc2cd4ff0e6e65ba49e1e97", size = 572431, upload-time = "2026-06-30T07:15:52.394Z" }, + { url = "https://files.pythonhosted.org/packages/85/ed/adab103321c0a6565d5ae1c2998349bc3ee175b82ccc5ae8fc04cc413075/rpds_py-2026.6.3-cp313-cp313-win32.whl", hash = "sha256:8c3d1e9c15b9d51ca0391e13da1a25a0a4df3c58a37c9dc368e0736cf7f69df0", size = 201710, upload-time = "2026-06-30T07:15:53.894Z" }, + { url = "https://files.pythonhosted.org/packages/7b/ed/a03b09668e74e5dabbf2e211f6468e1820c0552f7b0500082da31841bf7b/rpds_py-2026.6.3-cp313-cp313-win_amd64.whl", hash = "sha256:9250a9a0a6fd4648b3f868da8d91a4c52b5811a62df58e753d50ae4454a36f80", size = 219454, upload-time = "2026-06-30T07:15:55.25Z" }, + { url = "https://files.pythonhosted.org/packages/27/17/b8642c12930b71bc2b25831f6708ccf0f75abcd11883932ec9ce54ba3a78/rpds_py-2026.6.3-cp313-cp313-win_arm64.whl", hash = "sha256:900a67df3fd1660b035a4761c4ce73c382ea6b35f90f9863c36c6fd8bf8b09bb", size = 215063, upload-time = "2026-06-30T07:15:56.573Z" }, + { url = "https://files.pythonhosted.org/packages/b6/36/7fbe9dcdaf857fb3f63c2a2284b62492d95f5e8334e947e5fb6e7f68c9be/rpds_py-2026.6.3-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:931908d9fc855d8f74783377822be318edb6dcb19e47169dc038f9a1bf60b06e", size = 344510, upload-time = "2026-06-30T07:15:57.921Z" }, + { url = "https://files.pythonhosted.org/packages/ba/54/f785cc3d3f60839ca57a5af4927a9f347b07b2799c373fc20f7949f87c7e/rpds_py-2026.6.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:d7469697dce35be237db177d42e2a2ee26e6dcc5fc052078a6fefabd288c6edd", size = 339495, upload-time = "2026-06-30T07:15:59.238Z" }, + { url = "https://files.pythonhosted.org/packages/63/ef/d4cdaf309e6b095b43597103cf8c0b951d6cca2acce68c474f75ec12e0c7/rpds_py-2026.6.3-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bcfbcf66006befb9fd2aeaa9e01feaf881b4dc330a02ba07d2322b1c11be7b5d", size = 369454, upload-time = "2026-06-30T07:16:01.021Z" }, + { url = "https://files.pythonhosted.org/packages/96/4a/9559a68b7ee15db09d7981212e8c2e219d2a1d6d4faa0391d813c3496a36/rpds_py-2026.6.3-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:847927daf4cffbd4e90e42bc890069897101edd015f956cb8721b3473372edda", size = 374583, upload-time = "2026-06-30T07:16:02.287Z" }, + { url = "https://files.pythonhosted.org/packages/ef/75/8964aa7d2c6e8ac43eba8eb6e6b0fdda1f46d39f2fc3e6aa9f2cb17f485d/rpds_py-2026.6.3-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:aca6c1ef08a82bfe327cc156da694660f599923e2e6665b6d81c9c2d0ac9ffc8", size = 492919, upload-time = "2026-06-30T07:16:03.723Z" }, + { url = "https://files.pythonhosted.org/packages/8f/97/6908094ac804115e65aedfd90f1b5fee4eebebd3f6c4cfc5419939267565/rpds_py-2026.6.3-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ae50181a047c871561212bb97f7932a2d45fb53e947bd9b57ebad85b529cbc53", size = 383725, upload-time = "2026-06-30T07:16:05.305Z" }, + { url = "https://files.pythonhosted.org/packages/d1/9c/0d1fdc2e7aba23e290d603bc494e97bd205bae262ce33c6b32a69768ed5e/rpds_py-2026.6.3-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dc319e5a1de4b6913aac94bf6a2f9e847371e0a140a43dd4991db1a09bc2d504", size = 367255, upload-time = "2026-06-30T07:16:07.086Z" }, + { url = "https://files.pythonhosted.org/packages/c4/fe/f0209ca4a9ed074bc8acb44dfd0e81c3122e94c9689f5645b7973a866719/rpds_py-2026.6.3-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:e4316bf32babbed84e691e352faf967ce2f0f024174a8643c37c94a1080374fc", size = 379060, upload-time = "2026-06-30T07:16:08.525Z" }, + { url = "https://files.pythonhosted.org/packages/c6/8d/f1cc54c616b9d8897de8738aac148d20afca93f68187475fe194d09a71b9/rpds_py-2026.6.3-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8c6e5a2f750cc71c3e3b11d71661f21d6f9bc6cebc6564b1466417a1ec03ec77", size = 395960, upload-time = "2026-06-30T07:16:09.989Z" }, + { url = "https://files.pythonhosted.org/packages/fb/04/aafff00f73aeca2945f734f1d483c64ab8f472d0864ab02377fd8e89c3b2/rpds_py-2026.6.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:4470ce197d4090875cf6affbf1f853338387428df97c4fb7b7106317b8214698", size = 545356, upload-time = "2026-06-30T07:16:11.816Z" }, + { url = "https://files.pythonhosted.org/packages/fd/cc/e229663b9e4ddac5a4acbe9085dd80a71af2a5d356b8b39d6bff233f24b0/rpds_py-2026.6.3-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:ea964164cc9afa72d4d9b23cc28dafae93693c0a53e0b42acbff15b22c3f9ddd", size = 612319, upload-time = "2026-06-30T07:16:13.586Z" }, + { url = "https://files.pythonhosted.org/packages/e3/7a/8a0e6d3e6cd066af108b71b43122c3fe158dd9eb86acac626593a2582eb1/rpds_py-2026.6.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:639c8929aa0afe81be836b04de888460d6bed38b9c54cfc18da8f6bfabf5af5d", size = 573508, upload-time = "2026-06-30T07:16:15.23Z" }, + { url = "https://files.pythonhosted.org/packages/87/03/2a69ab618a789cf6cf85c86bb844c62d090e700ab1a2aa676b3741b6c516/rpds_py-2026.6.3-cp314-cp314-win32.whl", hash = "sha256:882076c00c0a608b131187055ddc5ae29f2e7eaf870d6168980420d58528a5c8", size = 202504, upload-time = "2026-06-30T07:16:16.893Z" }, + { url = "https://files.pythonhosted.org/packages/85/62/a3892ba945f4e24c78f352e5de3c7620d8479f73f211406a97263d13c7d2/rpds_py-2026.6.3-cp314-cp314-win_amd64.whl", hash = "sha256:0be972be84cfcaf46c8c6edf690ca0f154ac17babf1f6a955a51579b34ad2dc5", size = 220380, upload-time = "2026-06-30T07:16:18.108Z" }, + { url = "https://files.pythonhosted.org/packages/3d/e7/c2bd44dc831931815ad11ebb5f430b5a0a4d3caa9de837107876c30c3432/rpds_py-2026.6.3-cp314-cp314-win_arm64.whl", hash = "sha256:2a9c6f195058cb45335e8cc3802745c603d716eb96bc9625950c1aac71c0c703", size = 215976, upload-time = "2026-06-30T07:16:19.654Z" }, + { url = "https://files.pythonhosted.org/packages/79/9c/fff7b74bce9a091ec9a012a03f9ff5f69364eaf9451060dfc4486da2ffdd/rpds_py-2026.6.3-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:f90938e92afda60266da758ee7d363447f7f0138c9559f9e1811629580582d90", size = 346840, upload-time = "2026-06-30T07:16:21.268Z" }, + { url = "https://files.pythonhosted.org/packages/e9/44/77bcb1168b33704908295533d27f10eb811e9e3e193e8993dc99572211d3/rpds_py-2026.6.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:ec829541c45bca16e61c7ae50c20501f213605beb75d1aba91a6ee37fbbb56a4", size = 340282, upload-time = "2026-06-30T07:16:22.875Z" }, + { url = "https://files.pythonhosted.org/packages/87/3c/7a9081c7c9e645b39efe19e4ffbeccd80add246327cd9b888aecffd72317/rpds_py-2026.6.3-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:afd70d95892096cdb26f15a00c45907b17817577aa8d1c76b2dcc2788391f9e9", size = 370403, upload-time = "2026-06-30T07:16:24.415Z" }, + { url = "https://files.pythonhosted.org/packages/f7/69/af47021eb7dad6ff3396cb001c08f0f3c4d06c20253f75be6421a59fe6b7/rpds_py-2026.6.3-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:29dfa0533a5d4c94d4dfa1b694fcb56c9c63aad8330ffdd816fd225d0a7a162f", size = 376055, upload-time = "2026-06-30T07:16:26.111Z" }, + { url = "https://files.pythonhosted.org/packages/81/fc/a3bcf517084396a6dd258c592567a3c011ba4557f2fde23dceaf26e74f2e/rpds_py-2026.6.3-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:af05d726809bff6b141be124d4c7ce998f9c9c7f30edb1f46c07aa103d540b41", size = 494419, upload-time = "2026-06-30T07:16:27.596Z" }, + { url = "https://files.pythonhosted.org/packages/c9/eb/13d529d1788135425c7bf207f8463458ca5d92e43f3f701365b83e9dffc1/rpds_py-2026.6.3-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9826217f048f620d9a712672818bf231442c1b35d96b227a07eabd11b4bb6945", size = 384848, upload-time = "2026-06-30T07:16:29.183Z" }, + { url = "https://files.pythonhosted.org/packages/8e/f4/b7ac49f30013aba8f7b9566b1dd07e81de95e708c1374b7bacc5b9bc5c9c/rpds_py-2026.6.3-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:536bceea4fa4acf7e1c61da2b5786304367c816c8895be71b8f537c480b0ea1f", size = 371369, upload-time = "2026-06-30T07:16:30.912Z" }, + { url = "https://files.pythonhosted.org/packages/31/86/6260bafa622f788b07ddec0e52d810305c8b9b0b8c27f58a2ab04bf62b4f/rpds_py-2026.6.3-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:bc0011654b91cc4fb2ae701bec0a0ba1e552c0714247fa7af6c59e0ccfa3a4e1", size = 379673, upload-time = "2026-06-30T07:16:32.486Z" }, + { url = "https://files.pythonhosted.org/packages/19/c3/03f1ee79a047b48daeca157c89a18509cde22b6b951d642b9b0af1be660a/rpds_py-2026.6.3-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:539d75de9e0d536c84ff18dfeb805398e58227001ce09231a26a08b9aed1ee0e", size = 397500, upload-time = "2026-06-30T07:16:34.471Z" }, + { url = "https://files.pythonhosted.org/packages/f0/95/8ed0cd8c377dca12aea498f119fe639fc474d1461545c39d2b5872eb1c0f/rpds_py-2026.6.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:166cf54d9f44fc6ceb53c7860258dde44a81406646de79f8ed3234fca3b6e538", size = 545978, upload-time = "2026-06-30T07:16:36.45Z" }, + { url = "https://files.pythonhosted.org/packages/d3/f2/0eb57f0eaa83f8fc152a7e03de968ab77e1f00732bebc892b190c6eebde7/rpds_py-2026.6.3-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:d34c20167764fbcf927194d532dd7e0c56772f0a5f943fa5ef9e9afbba8fb9db", size = 613350, upload-time = "2026-06-30T07:16:38.213Z" }, + { url = "https://files.pythonhosted.org/packages/5b/de/e0674bdbc3ef7634989b3f854c3f34bc1f587d36e5bfdc5c378d57034619/rpds_py-2026.6.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ea7bb13b7c9a29791f87a0387ba7d3ad3a6d783d827e4d3f27b40a0ff44495e2", size = 576486, upload-time = "2026-06-30T07:16:39.797Z" }, + { url = "https://files.pythonhosted.org/packages/f2/f6/21101359743cd136ada781e8210a85769578422ba460672eea0e29739200/rpds_py-2026.6.3-cp314-cp314t-win32.whl", hash = "sha256:6de4744d05bd1aa1be4ed7ea1189e3979196808008113bbbf899a460966b925e", size = 201068, upload-time = "2026-06-30T07:16:41.316Z" }, + { url = "https://files.pythonhosted.org/packages/a6/b2/9574d4d44f7760c2aa32d92a0a4f41698e33f5b204a0bf5c9758f52c79d5/rpds_py-2026.6.3-cp314-cp314t-win_amd64.whl", hash = "sha256:c7b9a2f8f4d8e90af72571d3d495deebdd7e3c75451f5b41719aee166e940fc2", size = 220600, upload-time = "2026-06-30T07:16:43.091Z" }, + { url = "https://files.pythonhosted.org/packages/08/ae/f23a2697e6ee6340a578b0f136be6483657bef0c6f9497b752bb5c0964bb/rpds_py-2026.6.3-cp315-cp315-macosx_10_12_x86_64.whl", hash = "sha256:e059c5dde6452b44424bd1834557556c226b57781dee1227af23518459722b13", size = 344726, upload-time = "2026-06-30T07:16:44.5Z" }, + { url = "https://files.pythonhosted.org/packages/c3/63/e7b3a1a5358dd32c930a1062d8e15b67fd6e8922e81df9e91706d66ee5c8/rpds_py-2026.6.3-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:2f7c26fbc5acd2522b95d4177fe4710ffd8e9b20529e703ffbf8db4d93903f05", size = 339587, upload-time = "2026-06-30T07:16:46.255Z" }, + { url = "https://files.pythonhosted.org/packages/ec/64/10a85681916ca55fffb91b0a211f84e34297c109243484dd6394660a8a7c/rpds_py-2026.6.3-cp315-cp315-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a3086b538543802f84c843911242db20447de00d8752dd0efc936dbcf02218ba", size = 369585, upload-time = "2026-06-30T07:16:48.101Z" }, + { url = "https://files.pythonhosted.org/packages/76/c2/baf95c7c38823e12ba34407c5f5767a89e5cf2233895e56f608167ae9493/rpds_py-2026.6.3-cp315-cp315-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8f2e5c5ee828d42cb11760761c0af6507927bec42d0ad5458f97c9203b054617", size = 375479, upload-time = "2026-06-30T07:16:49.93Z" }, + { url = "https://files.pythonhosted.org/packages/6a/94/0aad06c72d65101e11d33528d438cda99a39ce0da99466e156158f2541d3/rpds_py-2026.6.3-cp315-cp315-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ed0c1e5d10cdc7135537988c74a0188da68e2f3c30813ba3744ab1e42e0480f9", size = 492418, upload-time = "2026-06-30T07:16:51.641Z" }, + { url = "https://files.pythonhosted.org/packages/b5/17/de3f5a479a1f056535d7489819639d8cd591ea6281d700390b43b1abd745/rpds_py-2026.6.3-cp315-cp315-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8c2642a7603ec0b16ed77da4555db3b4b472341904873788327c0b0d7b95f1bb", size = 384123, upload-time = "2026-06-30T07:16:53.622Z" }, + { url = "https://files.pythonhosted.org/packages/46/7d/bf09bd1b145bb2671c03e1e6d1ab8651858d90d8c7dfeadd85a37a934fd8/rpds_py-2026.6.3-cp315-cp315-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8e4320744c1ffdd95a603def63344bfab2d33edeab301c5007e7de9f9f5b3885", size = 367351, upload-time = "2026-06-30T07:16:55.241Z" }, + { url = "https://files.pythonhosted.org/packages/a3/ea/1bb734f314b8be319149ddee80b18bd41372bdcfbdf88d28131c0cd37719/rpds_py-2026.6.3-cp315-cp315-manylinux_2_31_riscv64.whl", hash = "sha256:a9f4645593036b81bbdb36b9c8e0ea0d1c3fee968c4d59db0344c14087ef143a", size = 378827, upload-time = "2026-06-30T07:16:56.841Z" }, + { url = "https://files.pythonhosted.org/packages/4b/93/d9611e5b25e26df9a3649813ed66193ace9347a7c7fc4ab7cf70e94851c0/rpds_py-2026.6.3-cp315-cp315-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e55d236be29255554da47abe5c577637db7c24a02b8b46f0ca9524c855801868", size = 395966, upload-time = "2026-06-30T07:16:58.557Z" }, + { url = "https://files.pythonhosted.org/packages/c3/cb/99d77e16e5534ae1d90629bbe419ba6ee170833a6a85e3aa1cc41726fbbc/rpds_py-2026.6.3-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:24e9c5386e16669b674a69c156c8eeefcb578f3b3397b713b08e6d60f3c7b187", size = 545680, upload-time = "2026-06-30T07:17:00.164Z" }, + { url = "https://files.pythonhosted.org/packages/59/15/11a29755f790cef7a2f755e8e14f4f0c33f39489e1893a632a2eee59672b/rpds_py-2026.6.3-cp315-cp315-musllinux_1_2_i686.whl", hash = "sha256:c60924535c75f1566b6eb75b5c31a48a43fef04fa2d0d201acbad8a9969c6107", size = 611853, upload-time = "2026-06-30T07:17:01.962Z" }, + { url = "https://files.pythonhosted.org/packages/68/86/0c27547e21644da938fb530f7e1a8148dd24d02db07e7a5f2567a17ce710/rpds_py-2026.6.3-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:38a2fea2787428f811719ceb9114cb78964a3138838320c29ac39526c79c16ba", size = 573715, upload-time = "2026-06-30T07:17:03.693Z" }, + { url = "https://files.pythonhosted.org/packages/29/71/4d8fcf700931815594bce892255bbd973b94efaf0fc1932b0590df18d886/rpds_py-2026.6.3-cp315-cp315-win32.whl", hash = "sha256:d483fe17f01ad64b7bf7cc38fcefff1ca9fb83f8c2b2542b68f97ffe0611b369", size = 202864, upload-time = "2026-06-30T07:17:05.746Z" }, + { url = "https://files.pythonhosted.org/packages/eb/62/b577562de0edbb55b2be85ce5fd09c33e386b9b13eee09833af4240fd5c4/rpds_py-2026.6.3-cp315-cp315-win_amd64.whl", hash = "sha256:67e3a721ffc5d8d2210d3671872298c4a84e4b8035cfe42ffd7cde35d772b146", size = 220430, upload-time = "2026-06-30T07:17:07.471Z" }, + { url = "https://files.pythonhosted.org/packages/c8/95/d6d0b2509825141eef60669a5739eec88dbc6a48053d6c92993a5704defe/rpds_py-2026.6.3-cp315-cp315-win_arm64.whl", hash = "sha256:6e84adbcf4bf841aed8116a8264b9f50b4cb3e7bd89b516122e616ac56ca269e", size = 215877, upload-time = "2026-06-30T07:17:09.008Z" }, + { url = "https://files.pythonhosted.org/packages/b7/bf/f3ea278f0afd615c1d0f19cb69043a41526e2bb600c2b536eb192218eb27/rpds_py-2026.6.3-cp315-cp315t-macosx_10_12_x86_64.whl", hash = "sha256:ae6dd8f10bd17aad820876d24caec9efdafd80a318d16c0a48edb5e136902c6b", size = 346933, upload-time = "2026-06-30T07:17:10.762Z" }, + { url = "https://files.pythonhosted.org/packages/9d/29/9907bdf1c5346763cf10b7f6852aad86652168c259def904cbe0082c5864/rpds_py-2026.6.3-cp315-cp315t-macosx_11_0_arm64.whl", hash = "sha256:bdbd97738551fca3917c1bd7188bec1920bb520104f28e7e1007f9ceb17b7690", size = 340274, upload-time = "2026-06-30T07:17:12.266Z" }, + { url = "https://files.pythonhosted.org/packages/6f/2c/8e03767b5778ef25cebf74a7a91a2c3806f8eced4c92cb7406bbe060756d/rpds_py-2026.6.3-cp315-cp315t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8b95977e7211527ab0ba576e286d023389fbeeb32a6b7b771665d333c60e5342", size = 370763, upload-time = "2026-06-30T07:17:14.107Z" }, + { url = "https://files.pythonhosted.org/packages/2e/e1/df2a7e1ba2efd796af26194250b8d42c821b46592311595162af9ef0528d/rpds_py-2026.6.3-cp315-cp315t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d15fde0e6fb0d88a60d221204873743e5d9f0b7d29165e62cd86d0413ad74ba6", size = 376467, upload-time = "2026-06-30T07:17:15.76Z" }, + { url = "https://files.pythonhosted.org/packages/6b/de/8a0814d1946af29cb068fb259aa8622f856df1d0bab58429448726b537f5/rpds_py-2026.6.3-cp315-cp315t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a136d453475ac0fcbda502ef1e6504bd28d6d904700915d278deeab0d00fe140", size = 496689, upload-time = "2026-06-30T07:17:17.308Z" }, + { url = "https://files.pythonhosted.org/packages/df/f3/f19e0c852ba13694f5a79f3b719331051573cb5693feacf8a88ffffc3a71/rpds_py-2026.6.3-cp315-cp315t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f826877d462181e5eb1c26a0026b8d0cab05d99844ecb6d8bf3627a2ca0c0442", size = 385340, upload-time = "2026-06-30T07:17:18.928Z" }, + { url = "https://files.pythonhosted.org/packages/e2/ae/7ec3a9d2d4351f99e37bcb06b6b6f954512646bfdbf9742e1de727865daf/rpds_py-2026.6.3-cp315-cp315t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:79486287de1730dbaff3dbd124d0ca4d2ef7f9d29bf2544f1f93c09b5bcbbd12", size = 372179, upload-time = "2026-06-30T07:17:20.539Z" }, + { url = "https://files.pythonhosted.org/packages/d3/ac/9cee911dff2aaa9a5a8354f6610bf2e6a616de9197c5fff4f54f82585f1e/rpds_py-2026.6.3-cp315-cp315t-manylinux_2_31_riscv64.whl", hash = "sha256:808345f53cb952433ca2816f1604ff3515608a81784954f38d4452acfe8e61d5", size = 379993, upload-time = "2026-06-30T07:17:22.212Z" }, + { url = "https://files.pythonhosted.org/packages/83/6b/7c2a07ba88d1e9a936612f7a5d067467ed03d971d5a06f7d309dff044a7e/rpds_py-2026.6.3-cp315-cp315t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1967debc37f64f2c4dc90a7f563aec558b471966e12adcac4e1c4240496b6ebf", size = 398909, upload-time = "2026-06-30T07:17:23.66Z" }, + { url = "https://files.pythonhosted.org/packages/97/0b/776ffcb66783637b0031f6d58d6fb55913c8b5abf00aeecd46bf933fb477/rpds_py-2026.6.3-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:f0840b5b17057f7fd918b76183a4b5a0635f43e14eb2ce60dce1d4ee4707ea00", size = 546584, upload-time = "2026-06-30T07:17:25.264Z" }, + { url = "https://files.pythonhosted.org/packages/55/33/ba3bc04d7092bd553c9b2b195624992d2cc4f3de1f380b7b93cbee67bd79/rpds_py-2026.6.3-cp315-cp315t-musllinux_1_2_i686.whl", hash = "sha256:faa679d19a6696fd54259ad321251ad77a13e70e03dd834daa762a44fb6196ef", size = 614357, upload-time = "2026-06-30T07:17:26.888Z" }, + { url = "https://files.pythonhosted.org/packages/8b/71/14edf065f04630b1a8472f7653cad03f6c478bcf95ea0e6aed55451e33ea/rpds_py-2026.6.3-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:23a439f31ccbeff1574e24889128821d1f7917470e830cf6544dced1c662262a", size = 576533, upload-time = "2026-06-30T07:17:28.546Z" }, + { url = "https://files.pythonhosted.org/packages/ba/76/65002b08596c389105720a8c0d22298b8dc25a4baf89b2ce431343c8b1de/rpds_py-2026.6.3-cp315-cp315t-win32.whl", hash = "sha256:913ca42ccad3f8cc6e292b587ae8ae49c8c823e5dce51a736252fc7c7cdfa577", size = 201204, upload-time = "2026-06-30T07:17:30.193Z" }, + { url = "https://files.pythonhosted.org/packages/8c/97/d855d6b3c322d1f27e26f5241c42016b56cf01377ea8ed348285f54652f0/rpds_py-2026.6.3-cp315-cp315t-win_amd64.whl", hash = "sha256:ae3d4fe8c0b9213624fdce7279d70e3b148b682ca20719ebd193a23ebfa47324", size = 220719, upload-time = "2026-06-30T07:17:31.788Z" }, + { url = "https://files.pythonhosted.org/packages/b4/9c/f0d19ac587fd0e4ab6b72cda355e9c5a6166b01ef7e064e437aef8eb9fef/rpds_py-2026.6.3-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:4cf2d36a2357e4d07bb5a4f98801265327b48256867816cfd2ceb001e9754a8f", size = 349791, upload-time = "2026-06-30T07:17:33.315Z" }, + { url = "https://files.pythonhosted.org/packages/38/c7/1d49d204c9fd2ee6c537601dc4c1ba921e03363ca576bfab94a00254ac9a/rpds_py-2026.6.3-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:30c6dc199b24a5e3e81d50da0f00858c5bbdb2617a750395687f4339c5818171", size = 352842, upload-time = "2026-06-30T07:17:34.897Z" }, + { url = "https://files.pythonhosted.org/packages/ac/e5/c0b5dc93cd0d4c06ce1f438907649514e2ea077bcd911e3154a51e96c38e/rpds_py-2026.6.3-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9891e594296ab9dada6551c8e7b387b2721f27a67eecd528412e8906247a7b90", size = 382094, upload-time = "2026-06-30T07:17:36.514Z" }, + { url = "https://files.pythonhosted.org/packages/0d/54/ec0e907b4ca8d541112db352409bd15f871c9b243e0c92c9b5a46ae96f01/rpds_py-2026.6.3-pp311-pypy311_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b5c2dc92304aa48a4a60443b548bb12f12e119d4b72f314015e67b9e1be97fca", size = 388662, upload-time = "2026-06-30T07:17:38.235Z" }, + { url = "https://files.pythonhosted.org/packages/d3/f4/921c22a4fd0f1c1ac13a3996ffbf0aa67951e2c8ad0d1d9574938a2932e8/rpds_py-2026.6.3-pp311-pypy311_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:127e08c0642d880cf32ca47ec2a4a77b901f7e2dd1ad9762adb13955d72ffcc9", size = 504896, upload-time = "2026-06-30T07:17:39.689Z" }, + { url = "https://files.pythonhosted.org/packages/0b/1b/a114b972cefa1ab1cdb3c7bb177cd3844a12826c507c722d3a73516dbbaf/rpds_py-2026.6.3-pp311-pypy311_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8bb68f03f395eb793220b45c097bd4d8c32944393da0fad8b999efac0868fc8c", size = 391545, upload-time = "2026-06-30T07:17:41.336Z" }, + { url = "https://files.pythonhosted.org/packages/4e/98/af9b3db77d47fcbe6c8c1f36e2c2147ec70292819e99c325f871584a1c11/rpds_py-2026.6.3-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a3450b693fde92133e9f51060568a4c31fcca76d5e53bbd611e689ca446517e9", size = 380059, upload-time = "2026-06-30T07:17:42.857Z" }, + { url = "https://files.pythonhosted.org/packages/c9/ba/0efd8668b97c1d26a61566386c636a7a7a09829e474fdf807caa15a2c844/rpds_py-2026.6.3-pp311-pypy311_pp73-manylinux_2_31_riscv64.whl", hash = "sha256:5e8d07bddee435a2ff6f1920e18feff28d0bc4533e42f4bf6927fbd073312c41", size = 393235, upload-time = "2026-06-30T07:17:44.637Z" }, + { url = "https://files.pythonhosted.org/packages/62/90/8c139ee9690f73b0829f32647de6f40d826f8f443af6fa72644f96351aac/rpds_py-2026.6.3-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:3a83ae6c67b7676b9878378547ca8e93ed77a580037bcbcd1d32f739e1e6089c", size = 413008, upload-time = "2026-06-30T07:17:46.225Z" }, + { url = "https://files.pythonhosted.org/packages/9c/97/0043896fdd7828ce09a1d9a8b06433714d0960fc4ff3fc4aa72b666b764e/rpds_py-2026.6.3-pp311-pypy311_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:2bfd04c19ddbd6640de0b51894d764bd2758854d5b75bd102d2ef10cb9c293a9", size = 558118, upload-time = "2026-06-30T07:17:47.759Z" }, + { url = "https://files.pythonhosted.org/packages/f6/40/02355f0e134f783a8f9814c4680a1bd311d37671577a5964ea838573ff37/rpds_py-2026.6.3-pp311-pypy311_pp73-musllinux_1_2_i686.whl", hash = "sha256:ca6546b66be9dc4738b1b043d5ebd5488c66c578c5ff0fd0e8065313fe3afb76", size = 623138, upload-time = "2026-06-30T07:17:49.355Z" }, + { url = "https://files.pythonhosted.org/packages/10/85/48f0abdcef5cce4e034c7a5b0ceeceba0b01bf0d942824f4bb720afe2dec/rpds_py-2026.6.3-pp311-pypy311_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:8e65860d238379ed982fd9ba690579b5e95af2f4840f99c772816dbe573cb826", size = 586486, upload-time = "2026-06-30T07:17:51.141Z" }, ] [[package]] @@ -1745,134 +2252,211 @@ dependencies = [ { name = "imageio" }, { name = "lazy-loader" }, { name = "networkx" }, - { name = "numpy" }, + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, + { name = "numpy", version = "2.5.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, { name = "packaging" }, { name = "pillow" }, - { name = "scipy" }, + { name = "scipy", version = "1.17.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, + { name = "scipy", version = "1.18.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, { name = "tifffile", version = "2026.3.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, - { name = "tifffile", version = "2026.4.11", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/a1/b4/2528bb43c67d48053a7a649a9666432dc307d66ba02e3a6d5c40f46655df/scikit_image-0.26.0.tar.gz", hash = "sha256:f5f970ab04efad85c24714321fcc91613fcb64ef2a892a13167df2f3e59199fa", size = 22729739 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/76/16/8a407688b607f86f81f8c649bf0d68a2a6d67375f18c2d660aba20f5b648/scikit_image-0.26.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b1ede33a0fb3731457eaf53af6361e73dd510f449dac437ab54573b26788baf0", size = 12355510 }, - { url = "https://files.pythonhosted.org/packages/6b/f9/7efc088ececb6f6868fd4475e16cfafc11f242ce9ab5fc3557d78b5da0d4/scikit_image-0.26.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7af7aa331c6846bd03fa28b164c18d0c3fd419dbb888fb05e958ac4257a78fdd", size = 12056334 }, - { url = "https://files.pythonhosted.org/packages/9f/1e/bc7fb91fb5ff65ef42346c8b7ee8b09b04eabf89235ab7dbfdfd96cbd1ea/scikit_image-0.26.0-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9ea6207d9e9d21c3f464efe733121c0504e494dbdc7728649ff3e23c3c5a4953", size = 13297768 }, - { url = "https://files.pythonhosted.org/packages/a5/2a/e71c1a7d90e70da67b88ccc609bd6ae54798d5847369b15d3a8052232f9d/scikit_image-0.26.0-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:74aa5518ccea28121f57a95374581d3b979839adc25bb03f289b1bc9b99c58af", size = 13711217 }, - { url = "https://files.pythonhosted.org/packages/d4/59/9637ee12c23726266b91296791465218973ce1ad3e4c56fc81e4d8e7d6e1/scikit_image-0.26.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d5c244656de905e195a904e36dbc18585e06ecf67d90f0482cbde63d7f9ad59d", size = 14337782 }, - { url = "https://files.pythonhosted.org/packages/e7/5c/a3e1e0860f9294663f540c117e4bf83d55e5b47c281d475cc06227e88411/scikit_image-0.26.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:21a818ee6ca2f2131b9e04d8eb7637b5c18773ebe7b399ad23dcc5afaa226d2d", size = 14805997 }, - { url = "https://files.pythonhosted.org/packages/d3/c6/2eeacf173da041a9e388975f54e5c49df750757fcfc3ee293cdbbae1ea0a/scikit_image-0.26.0-cp311-cp311-win_amd64.whl", hash = "sha256:9490360c8d3f9a7e85c8de87daf7c0c66507960cf4947bb9610d1751928721c7", size = 11878486 }, - { url = "https://files.pythonhosted.org/packages/c3/a4/a852c4949b9058d585e762a66bf7e9a2cd3be4795cd940413dfbfbb0ce79/scikit_image-0.26.0-cp311-cp311-win_arm64.whl", hash = "sha256:0baa0108d2d027f34d748e84e592b78acc23e965a5de0e4bb03cf371de5c0581", size = 11346518 }, - { url = "https://files.pythonhosted.org/packages/99/e8/e13757982264b33a1621628f86b587e9a73a13f5256dad49b19ba7dc9083/scikit_image-0.26.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d454b93a6fa770ac5ae2d33570f8e7a321bb80d29511ce4b6b78058ebe176e8c", size = 12376452 }, - { url = "https://files.pythonhosted.org/packages/e3/be/f8dd17d0510f9911f9f17ba301f7455328bf13dae416560126d428de9568/scikit_image-0.26.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3409e89d66eff5734cd2b672d1c48d2759360057e714e1d92a11df82c87cba37", size = 12061567 }, - { url = "https://files.pythonhosted.org/packages/b3/2b/c70120a6880579fb42b91567ad79feb4772f7be72e8d52fec403a3dde0c6/scikit_image-0.26.0-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4c717490cec9e276afb0438dd165b7c3072d6c416709cc0f9f5a4c1070d23a44", size = 13084214 }, - { url = "https://files.pythonhosted.org/packages/f4/a2/70401a107d6d7466d64b466927e6b96fcefa99d57494b972608e2f8be50f/scikit_image-0.26.0-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7df650e79031634ac90b11e64a9eedaf5a5e06fcd09bcd03a34be01745744466", size = 13561683 }, - { url = "https://files.pythonhosted.org/packages/13/a5/48bdfd92794c5002d664e0910a349d0a1504671ef5ad358150f21643c79a/scikit_image-0.26.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:cefd85033e66d4ea35b525bb0937d7f42d4cdcfed2d1888e1570d5ce450d3932", size = 14112147 }, - { url = "https://files.pythonhosted.org/packages/ee/b5/ac71694da92f5def5953ca99f18a10fe98eac2dd0a34079389b70b4d0394/scikit_image-0.26.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3f5bf622d7c0435884e1e141ebbe4b2804e16b2dd23ae4c6183e2ea99233be70", size = 14661625 }, - { url = "https://files.pythonhosted.org/packages/23/4d/a3cc1e96f080e253dad2251bfae7587cf2b7912bcd76fd43fd366ff35a87/scikit_image-0.26.0-cp312-cp312-win_amd64.whl", hash = "sha256:abed017474593cd3056ae0fe948d07d0747b27a085e92df5474f4955dd65aec0", size = 11911059 }, - { url = "https://files.pythonhosted.org/packages/35/8a/d1b8055f584acc937478abf4550d122936f420352422a1a625eef2c605d8/scikit_image-0.26.0-cp312-cp312-win_arm64.whl", hash = "sha256:4d57e39ef67a95d26860c8caf9b14b8fb130f83b34c6656a77f191fa6d1d04d8", size = 11348740 }, - { url = "https://files.pythonhosted.org/packages/4f/48/02357ffb2cca35640f33f2cfe054a4d6d5d7a229b88880a64f1e45c11f4e/scikit_image-0.26.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:a2e852eccf41d2d322b8e60144e124802873a92b8d43a6f96331aa42888491c7", size = 12346329 }, - { url = "https://files.pythonhosted.org/packages/67/b9/b792c577cea2c1e94cda83b135a656924fc57c428e8a6d302cd69aac1b60/scikit_image-0.26.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:98329aab3bc87db352b9887f64ce8cdb8e75f7c2daa19927f2e121b797b678d5", size = 12031726 }, - { url = "https://files.pythonhosted.org/packages/07/a9/9564250dfd65cb20404a611016db52afc6268b2b371cd19c7538ea47580f/scikit_image-0.26.0-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:915bb3ba66455cf8adac00dc8fdf18a4cd29656aec7ddd38cb4dda90289a6f21", size = 13094910 }, - { url = "https://files.pythonhosted.org/packages/a3/b8/0d8eeb5a9fd7d34ba84f8a55753a0a3e2b5b51b2a5a0ade648a8db4a62f7/scikit_image-0.26.0-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b36ab5e778bf50af5ff386c3ac508027dc3aaeccf2161bdf96bde6848f44d21b", size = 13660939 }, - { url = "https://files.pythonhosted.org/packages/2f/d6/91d8973584d4793d4c1a847d388e34ef1218d835eeddecfc9108d735b467/scikit_image-0.26.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:09bad6a5d5949c7896c8347424c4cca899f1d11668030e5548813ab9c2865dcb", size = 14138938 }, - { url = "https://files.pythonhosted.org/packages/39/9a/7e15d8dc10d6bbf212195fb39bdeb7f226c46dd53f9c63c312e111e2e175/scikit_image-0.26.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:aeb14db1ed09ad4bee4ceb9e635547a8d5f3549be67fc6c768c7f923e027e6cd", size = 14752243 }, - { url = "https://files.pythonhosted.org/packages/8f/58/2b11b933097bc427e42b4a8b15f7de8f24f2bac1fd2779d2aea1431b2c31/scikit_image-0.26.0-cp313-cp313-win_amd64.whl", hash = "sha256:ac529eb9dbd5954f9aaa2e3fe9a3fd9661bfe24e134c688587d811a0233127f1", size = 11906770 }, - { url = "https://files.pythonhosted.org/packages/ad/ec/96941474a18a04b69b6f6562a5bd79bd68049fa3728d3b350976eccb8b93/scikit_image-0.26.0-cp313-cp313-win_arm64.whl", hash = "sha256:a2d211bc355f59725efdcae699b93b30348a19416cc9e017f7b2fb599faf7219", size = 11342506 }, - { url = "https://files.pythonhosted.org/packages/03/e5/c1a9962b0cf1952f42d32b4a2e48eed520320dbc4d2ff0b981c6fa508b6b/scikit_image-0.26.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:9eefb4adad066da408a7601c4c24b07af3b472d90e08c3e7483d4e9e829d8c49", size = 12663278 }, - { url = "https://files.pythonhosted.org/packages/ae/97/c1a276a59ce8e4e24482d65c1a3940d69c6b3873279193b7ebd04e5ee56b/scikit_image-0.26.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:6caec76e16c970c528d15d1c757363334d5cb3069f9cea93d2bead31820511f3", size = 12405142 }, - { url = "https://files.pythonhosted.org/packages/d4/4a/f1cbd1357caef6c7993f7efd514d6e53d8fd6f7fe01c4714d51614c53289/scikit_image-0.26.0-cp313-cp313t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a07200fe09b9d99fcdab959859fe0f7db8df6333d6204344425d476850ce3604", size = 12942086 }, - { url = "https://files.pythonhosted.org/packages/5b/6f/74d9fb87c5655bd64cf00b0c44dc3d6206d9002e5f6ba1c9aeb13236f6bf/scikit_image-0.26.0-cp313-cp313t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:92242351bccf391fc5df2d1529d15470019496d2498d615beb68da85fe7fdf37", size = 13265667 }, - { url = "https://files.pythonhosted.org/packages/a7/73/faddc2413ae98d863f6fa2e3e14da4467dd38e788e1c23346cf1a2b06b97/scikit_image-0.26.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:52c496f75a7e45844d951557f13c08c81487c6a1da2e3c9c8a39fcde958e02cc", size = 14001966 }, - { url = "https://files.pythonhosted.org/packages/02/94/9f46966fa042b5d57c8cd641045372b4e0df0047dd400e77ea9952674110/scikit_image-0.26.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:20ef4a155e2e78b8ab973998e04d8a361d49d719e65412405f4dadd9155a61d9", size = 14359526 }, - { url = "https://files.pythonhosted.org/packages/5d/b4/2840fe38f10057f40b1c9f8fb98a187a370936bf144a4ac23452c5ef1baf/scikit_image-0.26.0-cp313-cp313t-win_amd64.whl", hash = "sha256:c9087cf7d0e7f33ab5c46d2068d86d785e70b05400a891f73a13400f1e1faf6a", size = 12287629 }, - { url = "https://files.pythonhosted.org/packages/22/ba/73b6ca70796e71f83ab222690e35a79612f0117e5aaf167151b7d46f5f2c/scikit_image-0.26.0-cp313-cp313t-win_arm64.whl", hash = "sha256:27d58bc8b2acd351f972c6508c1b557cfed80299826080a4d803dd29c51b707e", size = 11647755 }, - { url = "https://files.pythonhosted.org/packages/51/44/6b744f92b37ae2833fd423cce8f806d2368859ec325a699dc30389e090b9/scikit_image-0.26.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:63af3d3a26125f796f01052052f86806da5b5e54c6abef152edb752683075a9c", size = 12365810 }, - { url = "https://files.pythonhosted.org/packages/40/f5/83590d9355191f86ac663420fec741b82cc547a4afe7c4c1d986bf46e4db/scikit_image-0.26.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:ce00600cd70d4562ed59f80523e18cdcc1fae0e10676498a01f73c255774aefd", size = 12075717 }, - { url = "https://files.pythonhosted.org/packages/72/48/253e7cf5aee6190459fe136c614e2cbccc562deceb4af96e0863f1b8ee29/scikit_image-0.26.0-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6381edf972b32e4f54085449afde64365a57316637496c1325a736987083e2ab", size = 13161520 }, - { url = "https://files.pythonhosted.org/packages/73/c3/cec6a3cbaadfdcc02bd6ff02f3abfe09eaa7f4d4e0a525a1e3a3f4bce49c/scikit_image-0.26.0-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c6624a76c6085218248154cc7e1500e6b488edcd9499004dd0d35040607d7505", size = 13684340 }, - { url = "https://files.pythonhosted.org/packages/d4/0d/39a776f675d24164b3a267aa0db9f677a4cb20127660d8bf4fd7fef66817/scikit_image-0.26.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:f775f0e420faac9c2aa6757135f4eb468fb7b70e0b67fa77a5e79be3c30ee331", size = 14203839 }, - { url = "https://files.pythonhosted.org/packages/ee/25/2514df226bbcedfe9b2caafa1ba7bc87231a0c339066981b182b08340e06/scikit_image-0.26.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:ede4d6d255cc5da9faeb2f9ba7fedbc990abbc652db429f40a16b22e770bb578", size = 14770021 }, - { url = "https://files.pythonhosted.org/packages/8d/5b/0671dc91c0c79340c3fe202f0549c7d3681eb7640fe34ab68a5f090a7c7f/scikit_image-0.26.0-cp314-cp314-win_amd64.whl", hash = "sha256:0660b83968c15293fd9135e8d860053ee19500d52bf55ca4fb09de595a1af650", size = 12023490 }, - { url = "https://files.pythonhosted.org/packages/65/08/7c4cb59f91721f3de07719085212a0b3962e3e3f2d1818cbac4eeb1ea53e/scikit_image-0.26.0-cp314-cp314-win_arm64.whl", hash = "sha256:b8d14d3181c21c11170477a42542c1addc7072a90b986675a71266ad17abc37f", size = 11473782 }, - { url = "https://files.pythonhosted.org/packages/49/41/65c4258137acef3d73cb561ac55512eacd7b30bb4f4a11474cad526bc5db/scikit_image-0.26.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:cde0bbd57e6795eba83cb10f71a677f7239271121dc950bc060482834a668ad1", size = 12686060 }, - { url = "https://files.pythonhosted.org/packages/e7/32/76971f8727b87f1420a962406388a50e26667c31756126444baf6668f559/scikit_image-0.26.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:163e9afb5b879562b9aeda0dd45208a35316f26cc7a3aed54fd601604e5cf46f", size = 12422628 }, - { url = "https://files.pythonhosted.org/packages/37/0d/996febd39f757c40ee7b01cdb861867327e5c8e5f595a634e8201462d958/scikit_image-0.26.0-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:724f79fd9b6cb6f4a37864fe09f81f9f5d5b9646b6868109e1b100d1a7019e59", size = 12962369 }, - { url = "https://files.pythonhosted.org/packages/48/b4/612d354f946c9600e7dea012723c11d47e8d455384e530f6daaaeb9bf62c/scikit_image-0.26.0-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3268f13310e6857508bd87202620df996199a016a1d281b309441d227c822394", size = 13272431 }, - { url = "https://files.pythonhosted.org/packages/0a/6e/26c00b466e06055a086de2c6e2145fe189ccdc9a1d11ccc7de020f2591ad/scikit_image-0.26.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:fac96a1f9b06cd771cbbb3cd96c5332f36d4efd839b1d8b053f79e5887acde62", size = 14016362 }, - { url = "https://files.pythonhosted.org/packages/47/88/00a90402e1775634043c2a0af8a3c76ad450866d9fa444efcc43b553ba2d/scikit_image-0.26.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:2c1e7bd342f43e7a97e571b3f03ba4c1293ea1a35c3f13f41efdc8a81c1dc8f2", size = 14364151 }, - { url = "https://files.pythonhosted.org/packages/da/ca/918d8d306bd43beacff3b835c6d96fac0ae64c0857092f068b88db531a7c/scikit_image-0.26.0-cp314-cp314t-win_amd64.whl", hash = "sha256:b702c3bb115e1dcf4abf5297429b5c90f2189655888cbed14921f3d26f81d3a4", size = 12413484 }, - { url = "https://files.pythonhosted.org/packages/dc/cd/4da01329b5a8d47ff7ec3c99a2b02465a8017b186027590dc7425cee0b56/scikit_image-0.26.0-cp314-cp314t-win_arm64.whl", hash = "sha256:0608aa4a9ec39e0843de10d60edb2785a30c1c47819b67866dd223ebd149acaf", size = 11769501 }, + { name = "tifffile", version = "2026.7.14", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a1/b4/2528bb43c67d48053a7a649a9666432dc307d66ba02e3a6d5c40f46655df/scikit_image-0.26.0.tar.gz", hash = "sha256:f5f970ab04efad85c24714321fcc91613fcb64ef2a892a13167df2f3e59199fa", size = 22729739, upload-time = "2025-12-20T17:12:21.824Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/76/16/8a407688b607f86f81f8c649bf0d68a2a6d67375f18c2d660aba20f5b648/scikit_image-0.26.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b1ede33a0fb3731457eaf53af6361e73dd510f449dac437ab54573b26788baf0", size = 12355510, upload-time = "2025-12-20T17:10:31.628Z" }, + { url = "https://files.pythonhosted.org/packages/6b/f9/7efc088ececb6f6868fd4475e16cfafc11f242ce9ab5fc3557d78b5da0d4/scikit_image-0.26.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7af7aa331c6846bd03fa28b164c18d0c3fd419dbb888fb05e958ac4257a78fdd", size = 12056334, upload-time = "2025-12-20T17:10:34.559Z" }, + { url = "https://files.pythonhosted.org/packages/9f/1e/bc7fb91fb5ff65ef42346c8b7ee8b09b04eabf89235ab7dbfdfd96cbd1ea/scikit_image-0.26.0-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9ea6207d9e9d21c3f464efe733121c0504e494dbdc7728649ff3e23c3c5a4953", size = 13297768, upload-time = "2025-12-20T17:10:37.733Z" }, + { url = "https://files.pythonhosted.org/packages/a5/2a/e71c1a7d90e70da67b88ccc609bd6ae54798d5847369b15d3a8052232f9d/scikit_image-0.26.0-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:74aa5518ccea28121f57a95374581d3b979839adc25bb03f289b1bc9b99c58af", size = 13711217, upload-time = "2025-12-20T17:10:40.935Z" }, + { url = "https://files.pythonhosted.org/packages/d4/59/9637ee12c23726266b91296791465218973ce1ad3e4c56fc81e4d8e7d6e1/scikit_image-0.26.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d5c244656de905e195a904e36dbc18585e06ecf67d90f0482cbde63d7f9ad59d", size = 14337782, upload-time = "2025-12-20T17:10:43.452Z" }, + { url = "https://files.pythonhosted.org/packages/e7/5c/a3e1e0860f9294663f540c117e4bf83d55e5b47c281d475cc06227e88411/scikit_image-0.26.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:21a818ee6ca2f2131b9e04d8eb7637b5c18773ebe7b399ad23dcc5afaa226d2d", size = 14805997, upload-time = "2025-12-20T17:10:45.93Z" }, + { url = "https://files.pythonhosted.org/packages/d3/c6/2eeacf173da041a9e388975f54e5c49df750757fcfc3ee293cdbbae1ea0a/scikit_image-0.26.0-cp311-cp311-win_amd64.whl", hash = "sha256:9490360c8d3f9a7e85c8de87daf7c0c66507960cf4947bb9610d1751928721c7", size = 11878486, upload-time = "2025-12-20T17:10:48.246Z" }, + { url = "https://files.pythonhosted.org/packages/c3/a4/a852c4949b9058d585e762a66bf7e9a2cd3be4795cd940413dfbfbb0ce79/scikit_image-0.26.0-cp311-cp311-win_arm64.whl", hash = "sha256:0baa0108d2d027f34d748e84e592b78acc23e965a5de0e4bb03cf371de5c0581", size = 11346518, upload-time = "2025-12-20T17:10:50.575Z" }, + { url = "https://files.pythonhosted.org/packages/99/e8/e13757982264b33a1621628f86b587e9a73a13f5256dad49b19ba7dc9083/scikit_image-0.26.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d454b93a6fa770ac5ae2d33570f8e7a321bb80d29511ce4b6b78058ebe176e8c", size = 12376452, upload-time = "2025-12-20T17:10:52.796Z" }, + { url = "https://files.pythonhosted.org/packages/e3/be/f8dd17d0510f9911f9f17ba301f7455328bf13dae416560126d428de9568/scikit_image-0.26.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3409e89d66eff5734cd2b672d1c48d2759360057e714e1d92a11df82c87cba37", size = 12061567, upload-time = "2025-12-20T17:10:55.207Z" }, + { url = "https://files.pythonhosted.org/packages/b3/2b/c70120a6880579fb42b91567ad79feb4772f7be72e8d52fec403a3dde0c6/scikit_image-0.26.0-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4c717490cec9e276afb0438dd165b7c3072d6c416709cc0f9f5a4c1070d23a44", size = 13084214, upload-time = "2025-12-20T17:10:57.468Z" }, + { url = "https://files.pythonhosted.org/packages/f4/a2/70401a107d6d7466d64b466927e6b96fcefa99d57494b972608e2f8be50f/scikit_image-0.26.0-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7df650e79031634ac90b11e64a9eedaf5a5e06fcd09bcd03a34be01745744466", size = 13561683, upload-time = "2025-12-20T17:10:59.49Z" }, + { url = "https://files.pythonhosted.org/packages/13/a5/48bdfd92794c5002d664e0910a349d0a1504671ef5ad358150f21643c79a/scikit_image-0.26.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:cefd85033e66d4ea35b525bb0937d7f42d4cdcfed2d1888e1570d5ce450d3932", size = 14112147, upload-time = "2025-12-20T17:11:02.083Z" }, + { url = "https://files.pythonhosted.org/packages/ee/b5/ac71694da92f5def5953ca99f18a10fe98eac2dd0a34079389b70b4d0394/scikit_image-0.26.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3f5bf622d7c0435884e1e141ebbe4b2804e16b2dd23ae4c6183e2ea99233be70", size = 14661625, upload-time = "2025-12-20T17:11:04.528Z" }, + { url = "https://files.pythonhosted.org/packages/23/4d/a3cc1e96f080e253dad2251bfae7587cf2b7912bcd76fd43fd366ff35a87/scikit_image-0.26.0-cp312-cp312-win_amd64.whl", hash = "sha256:abed017474593cd3056ae0fe948d07d0747b27a085e92df5474f4955dd65aec0", size = 11911059, upload-time = "2025-12-20T17:11:06.61Z" }, + { url = "https://files.pythonhosted.org/packages/35/8a/d1b8055f584acc937478abf4550d122936f420352422a1a625eef2c605d8/scikit_image-0.26.0-cp312-cp312-win_arm64.whl", hash = "sha256:4d57e39ef67a95d26860c8caf9b14b8fb130f83b34c6656a77f191fa6d1d04d8", size = 11348740, upload-time = "2025-12-20T17:11:09.118Z" }, + { url = "https://files.pythonhosted.org/packages/4f/48/02357ffb2cca35640f33f2cfe054a4d6d5d7a229b88880a64f1e45c11f4e/scikit_image-0.26.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:a2e852eccf41d2d322b8e60144e124802873a92b8d43a6f96331aa42888491c7", size = 12346329, upload-time = "2025-12-20T17:11:11.599Z" }, + { url = "https://files.pythonhosted.org/packages/67/b9/b792c577cea2c1e94cda83b135a656924fc57c428e8a6d302cd69aac1b60/scikit_image-0.26.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:98329aab3bc87db352b9887f64ce8cdb8e75f7c2daa19927f2e121b797b678d5", size = 12031726, upload-time = "2025-12-20T17:11:13.871Z" }, + { url = "https://files.pythonhosted.org/packages/07/a9/9564250dfd65cb20404a611016db52afc6268b2b371cd19c7538ea47580f/scikit_image-0.26.0-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:915bb3ba66455cf8adac00dc8fdf18a4cd29656aec7ddd38cb4dda90289a6f21", size = 13094910, upload-time = "2025-12-20T17:11:16.2Z" }, + { url = "https://files.pythonhosted.org/packages/a3/b8/0d8eeb5a9fd7d34ba84f8a55753a0a3e2b5b51b2a5a0ade648a8db4a62f7/scikit_image-0.26.0-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b36ab5e778bf50af5ff386c3ac508027dc3aaeccf2161bdf96bde6848f44d21b", size = 13660939, upload-time = "2025-12-20T17:11:18.464Z" }, + { url = "https://files.pythonhosted.org/packages/2f/d6/91d8973584d4793d4c1a847d388e34ef1218d835eeddecfc9108d735b467/scikit_image-0.26.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:09bad6a5d5949c7896c8347424c4cca899f1d11668030e5548813ab9c2865dcb", size = 14138938, upload-time = "2025-12-20T17:11:20.919Z" }, + { url = "https://files.pythonhosted.org/packages/39/9a/7e15d8dc10d6bbf212195fb39bdeb7f226c46dd53f9c63c312e111e2e175/scikit_image-0.26.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:aeb14db1ed09ad4bee4ceb9e635547a8d5f3549be67fc6c768c7f923e027e6cd", size = 14752243, upload-time = "2025-12-20T17:11:23.347Z" }, + { url = "https://files.pythonhosted.org/packages/8f/58/2b11b933097bc427e42b4a8b15f7de8f24f2bac1fd2779d2aea1431b2c31/scikit_image-0.26.0-cp313-cp313-win_amd64.whl", hash = "sha256:ac529eb9dbd5954f9aaa2e3fe9a3fd9661bfe24e134c688587d811a0233127f1", size = 11906770, upload-time = "2025-12-20T17:11:25.297Z" }, + { url = "https://files.pythonhosted.org/packages/ad/ec/96941474a18a04b69b6f6562a5bd79bd68049fa3728d3b350976eccb8b93/scikit_image-0.26.0-cp313-cp313-win_arm64.whl", hash = "sha256:a2d211bc355f59725efdcae699b93b30348a19416cc9e017f7b2fb599faf7219", size = 11342506, upload-time = "2025-12-20T17:11:27.399Z" }, + { url = "https://files.pythonhosted.org/packages/03/e5/c1a9962b0cf1952f42d32b4a2e48eed520320dbc4d2ff0b981c6fa508b6b/scikit_image-0.26.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:9eefb4adad066da408a7601c4c24b07af3b472d90e08c3e7483d4e9e829d8c49", size = 12663278, upload-time = "2025-12-20T17:11:29.358Z" }, + { url = "https://files.pythonhosted.org/packages/ae/97/c1a276a59ce8e4e24482d65c1a3940d69c6b3873279193b7ebd04e5ee56b/scikit_image-0.26.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:6caec76e16c970c528d15d1c757363334d5cb3069f9cea93d2bead31820511f3", size = 12405142, upload-time = "2025-12-20T17:11:31.282Z" }, + { url = "https://files.pythonhosted.org/packages/d4/4a/f1cbd1357caef6c7993f7efd514d6e53d8fd6f7fe01c4714d51614c53289/scikit_image-0.26.0-cp313-cp313t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a07200fe09b9d99fcdab959859fe0f7db8df6333d6204344425d476850ce3604", size = 12942086, upload-time = "2025-12-20T17:11:33.683Z" }, + { url = "https://files.pythonhosted.org/packages/5b/6f/74d9fb87c5655bd64cf00b0c44dc3d6206d9002e5f6ba1c9aeb13236f6bf/scikit_image-0.26.0-cp313-cp313t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:92242351bccf391fc5df2d1529d15470019496d2498d615beb68da85fe7fdf37", size = 13265667, upload-time = "2025-12-20T17:11:36.11Z" }, + { url = "https://files.pythonhosted.org/packages/a7/73/faddc2413ae98d863f6fa2e3e14da4467dd38e788e1c23346cf1a2b06b97/scikit_image-0.26.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:52c496f75a7e45844d951557f13c08c81487c6a1da2e3c9c8a39fcde958e02cc", size = 14001966, upload-time = "2025-12-20T17:11:38.55Z" }, + { url = "https://files.pythonhosted.org/packages/02/94/9f46966fa042b5d57c8cd641045372b4e0df0047dd400e77ea9952674110/scikit_image-0.26.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:20ef4a155e2e78b8ab973998e04d8a361d49d719e65412405f4dadd9155a61d9", size = 14359526, upload-time = "2025-12-20T17:11:41.087Z" }, + { url = "https://files.pythonhosted.org/packages/5d/b4/2840fe38f10057f40b1c9f8fb98a187a370936bf144a4ac23452c5ef1baf/scikit_image-0.26.0-cp313-cp313t-win_amd64.whl", hash = "sha256:c9087cf7d0e7f33ab5c46d2068d86d785e70b05400a891f73a13400f1e1faf6a", size = 12287629, upload-time = "2025-12-20T17:11:43.11Z" }, + { url = "https://files.pythonhosted.org/packages/22/ba/73b6ca70796e71f83ab222690e35a79612f0117e5aaf167151b7d46f5f2c/scikit_image-0.26.0-cp313-cp313t-win_arm64.whl", hash = "sha256:27d58bc8b2acd351f972c6508c1b557cfed80299826080a4d803dd29c51b707e", size = 11647755, upload-time = "2025-12-20T17:11:45.279Z" }, + { url = "https://files.pythonhosted.org/packages/51/44/6b744f92b37ae2833fd423cce8f806d2368859ec325a699dc30389e090b9/scikit_image-0.26.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:63af3d3a26125f796f01052052f86806da5b5e54c6abef152edb752683075a9c", size = 12365810, upload-time = "2025-12-20T17:11:47.357Z" }, + { url = "https://files.pythonhosted.org/packages/40/f5/83590d9355191f86ac663420fec741b82cc547a4afe7c4c1d986bf46e4db/scikit_image-0.26.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:ce00600cd70d4562ed59f80523e18cdcc1fae0e10676498a01f73c255774aefd", size = 12075717, upload-time = "2025-12-20T17:11:49.483Z" }, + { url = "https://files.pythonhosted.org/packages/72/48/253e7cf5aee6190459fe136c614e2cbccc562deceb4af96e0863f1b8ee29/scikit_image-0.26.0-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6381edf972b32e4f54085449afde64365a57316637496c1325a736987083e2ab", size = 13161520, upload-time = "2025-12-20T17:11:51.58Z" }, + { url = "https://files.pythonhosted.org/packages/73/c3/cec6a3cbaadfdcc02bd6ff02f3abfe09eaa7f4d4e0a525a1e3a3f4bce49c/scikit_image-0.26.0-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c6624a76c6085218248154cc7e1500e6b488edcd9499004dd0d35040607d7505", size = 13684340, upload-time = "2025-12-20T17:11:53.708Z" }, + { url = "https://files.pythonhosted.org/packages/d4/0d/39a776f675d24164b3a267aa0db9f677a4cb20127660d8bf4fd7fef66817/scikit_image-0.26.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:f775f0e420faac9c2aa6757135f4eb468fb7b70e0b67fa77a5e79be3c30ee331", size = 14203839, upload-time = "2025-12-20T17:11:55.89Z" }, + { url = "https://files.pythonhosted.org/packages/ee/25/2514df226bbcedfe9b2caafa1ba7bc87231a0c339066981b182b08340e06/scikit_image-0.26.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:ede4d6d255cc5da9faeb2f9ba7fedbc990abbc652db429f40a16b22e770bb578", size = 14770021, upload-time = "2025-12-20T17:11:58.014Z" }, + { url = "https://files.pythonhosted.org/packages/8d/5b/0671dc91c0c79340c3fe202f0549c7d3681eb7640fe34ab68a5f090a7c7f/scikit_image-0.26.0-cp314-cp314-win_amd64.whl", hash = "sha256:0660b83968c15293fd9135e8d860053ee19500d52bf55ca4fb09de595a1af650", size = 12023490, upload-time = "2025-12-20T17:12:00.013Z" }, + { url = "https://files.pythonhosted.org/packages/65/08/7c4cb59f91721f3de07719085212a0b3962e3e3f2d1818cbac4eeb1ea53e/scikit_image-0.26.0-cp314-cp314-win_arm64.whl", hash = "sha256:b8d14d3181c21c11170477a42542c1addc7072a90b986675a71266ad17abc37f", size = 11473782, upload-time = "2025-12-20T17:12:01.983Z" }, + { url = "https://files.pythonhosted.org/packages/49/41/65c4258137acef3d73cb561ac55512eacd7b30bb4f4a11474cad526bc5db/scikit_image-0.26.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:cde0bbd57e6795eba83cb10f71a677f7239271121dc950bc060482834a668ad1", size = 12686060, upload-time = "2025-12-20T17:12:03.886Z" }, + { url = "https://files.pythonhosted.org/packages/e7/32/76971f8727b87f1420a962406388a50e26667c31756126444baf6668f559/scikit_image-0.26.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:163e9afb5b879562b9aeda0dd45208a35316f26cc7a3aed54fd601604e5cf46f", size = 12422628, upload-time = "2025-12-20T17:12:05.921Z" }, + { url = "https://files.pythonhosted.org/packages/37/0d/996febd39f757c40ee7b01cdb861867327e5c8e5f595a634e8201462d958/scikit_image-0.26.0-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:724f79fd9b6cb6f4a37864fe09f81f9f5d5b9646b6868109e1b100d1a7019e59", size = 12962369, upload-time = "2025-12-20T17:12:07.912Z" }, + { url = "https://files.pythonhosted.org/packages/48/b4/612d354f946c9600e7dea012723c11d47e8d455384e530f6daaaeb9bf62c/scikit_image-0.26.0-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3268f13310e6857508bd87202620df996199a016a1d281b309441d227c822394", size = 13272431, upload-time = "2025-12-20T17:12:10.255Z" }, + { url = "https://files.pythonhosted.org/packages/0a/6e/26c00b466e06055a086de2c6e2145fe189ccdc9a1d11ccc7de020f2591ad/scikit_image-0.26.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:fac96a1f9b06cd771cbbb3cd96c5332f36d4efd839b1d8b053f79e5887acde62", size = 14016362, upload-time = "2025-12-20T17:12:12.793Z" }, + { url = "https://files.pythonhosted.org/packages/47/88/00a90402e1775634043c2a0af8a3c76ad450866d9fa444efcc43b553ba2d/scikit_image-0.26.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:2c1e7bd342f43e7a97e571b3f03ba4c1293ea1a35c3f13f41efdc8a81c1dc8f2", size = 14364151, upload-time = "2025-12-20T17:12:14.909Z" }, + { url = "https://files.pythonhosted.org/packages/da/ca/918d8d306bd43beacff3b835c6d96fac0ae64c0857092f068b88db531a7c/scikit_image-0.26.0-cp314-cp314t-win_amd64.whl", hash = "sha256:b702c3bb115e1dcf4abf5297429b5c90f2189655888cbed14921f3d26f81d3a4", size = 12413484, upload-time = "2025-12-20T17:12:17.046Z" }, + { url = "https://files.pythonhosted.org/packages/dc/cd/4da01329b5a8d47ff7ec3c99a2b02465a8017b186027590dc7425cee0b56/scikit_image-0.26.0-cp314-cp314t-win_arm64.whl", hash = "sha256:0608aa4a9ec39e0843de10d60edb2785a30c1c47819b67866dd223ebd149acaf", size = 11769501, upload-time = "2025-12-20T17:12:19.339Z" }, ] [[package]] name = "scipy" version = "1.17.1" 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 == 'emscripten'", + "python_full_version < '3.12' and sys_platform != 'darwin' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version < '3.12' and sys_platform == 'darwin'", +] dependencies = [ - { name = "numpy" }, + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" } }, +] +sdist = { url = "https://files.pythonhosted.org/packages/7a/97/5a3609c4f8d58b039179648e62dd220f89864f56f7357f5d4f45c29eb2cc/scipy-1.17.1.tar.gz", hash = "sha256:95d8e012d8cb8816c226aef832200b1d45109ed4464303e997c5b13122b297c0", size = 30573822, upload-time = "2026-02-23T00:26:24.851Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/df/75/b4ce781849931fef6fd529afa6b63711d5a733065722d0c3e2724af9e40a/scipy-1.17.1-cp311-cp311-macosx_10_14_x86_64.whl", hash = "sha256:1f95b894f13729334fb990162e911c9e5dc1ab390c58aa6cbecb389c5b5e28ec", size = 31613675, upload-time = "2026-02-23T00:16:00.13Z" }, + { url = "https://files.pythonhosted.org/packages/f7/58/bccc2861b305abdd1b8663d6130c0b3d7cc22e8d86663edbc8401bfd40d4/scipy-1.17.1-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:e18f12c6b0bc5a592ed23d3f7b891f68fd7f8241d69b7883769eb5d5dfb52696", size = 28162057, upload-time = "2026-02-23T00:16:09.456Z" }, + { url = "https://files.pythonhosted.org/packages/6d/ee/18146b7757ed4976276b9c9819108adbc73c5aad636e5353e20746b73069/scipy-1.17.1-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:a3472cfbca0a54177d0faa68f697d8ba4c80bbdc19908c3465556d9f7efce9ee", size = 20334032, upload-time = "2026-02-23T00:16:17.358Z" }, + { url = "https://files.pythonhosted.org/packages/ec/e6/cef1cf3557f0c54954198554a10016b6a03b2ec9e22a4e1df734936bd99c/scipy-1.17.1-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:766e0dc5a616d026a3a1cffa379af959671729083882f50307e18175797b3dfd", size = 22709533, upload-time = "2026-02-23T00:16:25.791Z" }, + { url = "https://files.pythonhosted.org/packages/4d/60/8804678875fc59362b0fb759ab3ecce1f09c10a735680318ac30da8cd76b/scipy-1.17.1-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:744b2bf3640d907b79f3fd7874efe432d1cf171ee721243e350f55234b4cec4c", size = 33062057, upload-time = "2026-02-23T00:16:36.931Z" }, + { url = "https://files.pythonhosted.org/packages/09/7d/af933f0f6e0767995b4e2d705a0665e454d1c19402aa7e895de3951ebb04/scipy-1.17.1-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:43af8d1f3bea642559019edfe64e9b11192a8978efbd1539d7bc2aaa23d92de4", size = 35349300, upload-time = "2026-02-23T00:16:49.108Z" }, + { url = "https://files.pythonhosted.org/packages/b4/3d/7ccbbdcbb54c8fdc20d3b6930137c782a163fa626f0aef920349873421ba/scipy-1.17.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:cd96a1898c0a47be4520327e01f874acfd61fb48a9420f8aa9f6483412ffa444", size = 35127333, upload-time = "2026-02-23T00:17:01.293Z" }, + { url = "https://files.pythonhosted.org/packages/e8/19/f926cb11c42b15ba08e3a71e376d816ac08614f769b4f47e06c3580c836a/scipy-1.17.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:4eb6c25dd62ee8d5edf68a8e1c171dd71c292fdae95d8aeb3dd7d7de4c364082", size = 37741314, upload-time = "2026-02-23T00:17:12.576Z" }, + { url = "https://files.pythonhosted.org/packages/95/da/0d1df507cf574b3f224ccc3d45244c9a1d732c81dcb26b1e8a766ae271a8/scipy-1.17.1-cp311-cp311-win_amd64.whl", hash = "sha256:d30e57c72013c2a4fe441c2fcb8e77b14e152ad48b5464858e07e2ad9fbfceff", size = 36607512, upload-time = "2026-02-23T00:17:23.424Z" }, + { url = "https://files.pythonhosted.org/packages/68/7f/bdd79ceaad24b671543ffe0ef61ed8e659440eb683b66f033454dcee90eb/scipy-1.17.1-cp311-cp311-win_arm64.whl", hash = "sha256:9ecb4efb1cd6e8c4afea0daa91a87fbddbce1b99d2895d151596716c0b2e859d", size = 24599248, upload-time = "2026-02-23T00:17:34.561Z" }, + { url = "https://files.pythonhosted.org/packages/35/48/b992b488d6f299dbe3f11a20b24d3dda3d46f1a635ede1c46b5b17a7b163/scipy-1.17.1-cp312-cp312-macosx_10_14_x86_64.whl", hash = "sha256:35c3a56d2ef83efc372eaec584314bd0ef2e2f0d2adb21c55e6ad5b344c0dcb8", size = 31610954, upload-time = "2026-02-23T00:17:49.855Z" }, + { url = "https://files.pythonhosted.org/packages/b2/02/cf107b01494c19dc100f1d0b7ac3cc08666e96ba2d64db7626066cee895e/scipy-1.17.1-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:fcb310ddb270a06114bb64bbe53c94926b943f5b7f0842194d585c65eb4edd76", size = 28172662, upload-time = "2026-02-23T00:18:01.64Z" }, + { url = "https://files.pythonhosted.org/packages/cf/a9/599c28631bad314d219cf9ffd40e985b24d603fc8a2f4ccc5ae8419a535b/scipy-1.17.1-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:cc90d2e9c7e5c7f1a482c9875007c095c3194b1cfedca3c2f3291cdc2bc7c086", size = 20344366, upload-time = "2026-02-23T00:18:12.015Z" }, + { url = "https://files.pythonhosted.org/packages/35/f5/906eda513271c8deb5af284e5ef0206d17a96239af79f9fa0aebfe0e36b4/scipy-1.17.1-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:c80be5ede8f3f8eded4eff73cc99a25c388ce98e555b17d31da05287015ffa5b", size = 22704017, upload-time = "2026-02-23T00:18:21.502Z" }, + { url = "https://files.pythonhosted.org/packages/da/34/16f10e3042d2f1d6b66e0428308ab52224b6a23049cb2f5c1756f713815f/scipy-1.17.1-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e19ebea31758fac5893a2ac360fedd00116cbb7628e650842a6691ba7ca28a21", size = 32927842, upload-time = "2026-02-23T00:18:35.367Z" }, + { url = "https://files.pythonhosted.org/packages/01/8e/1e35281b8ab6d5d72ebe9911edcdffa3f36b04ed9d51dec6dd140396e220/scipy-1.17.1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:02ae3b274fde71c5e92ac4d54bc06c42d80e399fec704383dcd99b301df37458", size = 35235890, upload-time = "2026-02-23T00:18:49.188Z" }, + { url = "https://files.pythonhosted.org/packages/c5/5c/9d7f4c88bea6e0d5a4f1bc0506a53a00e9fcb198de372bfe4d3652cef482/scipy-1.17.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8a604bae87c6195d8b1045eddece0514d041604b14f2727bbc2b3020172045eb", size = 35003557, upload-time = "2026-02-23T00:18:54.74Z" }, + { url = "https://files.pythonhosted.org/packages/65/94/7698add8f276dbab7a9de9fb6b0e02fc13ee61d51c7c3f85ac28b65e1239/scipy-1.17.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:f590cd684941912d10becc07325a3eeb77886fe981415660d9265c4c418d0bea", size = 37625856, upload-time = "2026-02-23T00:19:00.307Z" }, + { url = "https://files.pythonhosted.org/packages/a2/84/dc08d77fbf3d87d3ee27f6a0c6dcce1de5829a64f2eae85a0ecc1f0daa73/scipy-1.17.1-cp312-cp312-win_amd64.whl", hash = "sha256:41b71f4a3a4cab9d366cd9065b288efc4d4f3c0b37a91a8e0947fb5bd7f31d87", size = 36549682, upload-time = "2026-02-23T00:19:07.67Z" }, + { url = "https://files.pythonhosted.org/packages/bc/98/fe9ae9ffb3b54b62559f52dedaebe204b408db8109a8c66fdd04869e6424/scipy-1.17.1-cp312-cp312-win_arm64.whl", hash = "sha256:f4115102802df98b2b0db3cce5cb9b92572633a1197c77b7553e5203f284a5b3", size = 24547340, upload-time = "2026-02-23T00:19:12.024Z" }, + { url = "https://files.pythonhosted.org/packages/76/27/07ee1b57b65e92645f219b37148a7e7928b82e2b5dbeccecb4dff7c64f0b/scipy-1.17.1-cp313-cp313-macosx_10_14_x86_64.whl", hash = "sha256:5e3c5c011904115f88a39308379c17f91546f77c1667cea98739fe0fccea804c", size = 31590199, upload-time = "2026-02-23T00:19:17.192Z" }, + { url = "https://files.pythonhosted.org/packages/ec/ae/db19f8ab842e9b724bf5dbb7db29302a91f1e55bc4d04b1025d6d605a2c5/scipy-1.17.1-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:6fac755ca3d2c3edcb22f479fceaa241704111414831ddd3bc6056e18516892f", size = 28154001, upload-time = "2026-02-23T00:19:22.241Z" }, + { url = "https://files.pythonhosted.org/packages/5b/58/3ce96251560107b381cbd6e8413c483bbb1228a6b919fa8652b0d4090e7f/scipy-1.17.1-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:7ff200bf9d24f2e4d5dc6ee8c3ac64d739d3a89e2326ba68aaf6c4a2b838fd7d", size = 20325719, upload-time = "2026-02-23T00:19:26.329Z" }, + { url = "https://files.pythonhosted.org/packages/b2/83/15087d945e0e4d48ce2377498abf5ad171ae013232ae31d06f336e64c999/scipy-1.17.1-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:4b400bdc6f79fa02a4d86640310dde87a21fba0c979efff5248908c6f15fad1b", size = 22683595, upload-time = "2026-02-23T00:19:30.304Z" }, + { url = "https://files.pythonhosted.org/packages/b4/e0/e58fbde4a1a594c8be8114eb4aac1a55bcd6587047efc18a61eb1f5c0d30/scipy-1.17.1-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2b64ca7d4aee0102a97f3ba22124052b4bd2152522355073580bf4845e2550b6", size = 32896429, upload-time = "2026-02-23T00:19:35.536Z" }, + { url = "https://files.pythonhosted.org/packages/f5/5f/f17563f28ff03c7b6799c50d01d5d856a1d55f2676f537ca8d28c7f627cd/scipy-1.17.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:581b2264fc0aa555f3f435a5944da7504ea3a065d7029ad60e7c3d1ae09c5464", size = 35203952, upload-time = "2026-02-23T00:19:42.259Z" }, + { url = "https://files.pythonhosted.org/packages/8d/a5/9afd17de24f657fdfe4df9a3f1ea049b39aef7c06000c13db1530d81ccca/scipy-1.17.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:beeda3d4ae615106d7094f7e7cef6218392e4465cc95d25f900bebabfded0950", size = 34979063, upload-time = "2026-02-23T00:19:47.547Z" }, + { url = "https://files.pythonhosted.org/packages/8b/13/88b1d2384b424bf7c924f2038c1c409f8d88bb2a8d49d097861dd64a57b2/scipy-1.17.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6609bc224e9568f65064cfa72edc0f24ee6655b47575954ec6339534b2798369", size = 37598449, upload-time = "2026-02-23T00:19:53.238Z" }, + { url = "https://files.pythonhosted.org/packages/35/e5/d6d0e51fc888f692a35134336866341c08655d92614f492c6860dc45bb2c/scipy-1.17.1-cp313-cp313-win_amd64.whl", hash = "sha256:37425bc9175607b0268f493d79a292c39f9d001a357bebb6b88fdfaff13f6448", size = 36510943, upload-time = "2026-02-23T00:20:50.89Z" }, + { url = "https://files.pythonhosted.org/packages/2a/fd/3be73c564e2a01e690e19cc618811540ba5354c67c8680dce3281123fb79/scipy-1.17.1-cp313-cp313-win_arm64.whl", hash = "sha256:5cf36e801231b6a2059bf354720274b7558746f3b1a4efb43fcf557ccd484a87", size = 24545621, upload-time = "2026-02-23T00:20:55.871Z" }, + { url = "https://files.pythonhosted.org/packages/6f/6b/17787db8b8114933a66f9dcc479a8272e4b4da75fe03b0c282f7b0ade8cd/scipy-1.17.1-cp313-cp313t-macosx_10_14_x86_64.whl", hash = "sha256:d59c30000a16d8edc7e64152e30220bfbd724c9bbb08368c054e24c651314f0a", size = 31936708, upload-time = "2026-02-23T00:19:58.694Z" }, + { url = "https://files.pythonhosted.org/packages/38/2e/524405c2b6392765ab1e2b722a41d5da33dc5c7b7278184a8ad29b6cb206/scipy-1.17.1-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:010f4333c96c9bb1a4516269e33cb5917b08ef2166d5556ca2fd9f082a9e6ea0", size = 28570135, upload-time = "2026-02-23T00:20:03.934Z" }, + { url = "https://files.pythonhosted.org/packages/fd/c3/5bd7199f4ea8556c0c8e39f04ccb014ac37d1468e6cfa6a95c6b3562b76e/scipy-1.17.1-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:2ceb2d3e01c5f1d83c4189737a42d9cb2fc38a6eeed225e7515eef71ad301dce", size = 20741977, upload-time = "2026-02-23T00:20:07.935Z" }, + { url = "https://files.pythonhosted.org/packages/d9/b8/8ccd9b766ad14c78386599708eb745f6b44f08400a5fd0ade7cf89b6fc93/scipy-1.17.1-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:844e165636711ef41f80b4103ed234181646b98a53c8f05da12ca5ca289134f6", size = 23029601, upload-time = "2026-02-23T00:20:12.161Z" }, + { url = "https://files.pythonhosted.org/packages/6d/a0/3cb6f4d2fb3e17428ad2880333cac878909ad1a89f678527b5328b93c1d4/scipy-1.17.1-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:158dd96d2207e21c966063e1635b1063cd7787b627b6f07305315dd73d9c679e", size = 33019667, upload-time = "2026-02-23T00:20:17.208Z" }, + { url = "https://files.pythonhosted.org/packages/f3/c3/2d834a5ac7bf3a0c806ad1508efc02dda3c8c61472a56132d7894c312dea/scipy-1.17.1-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:74cbb80d93260fe2ffa334efa24cb8f2f0f622a9b9febf8b483c0b865bfb3475", size = 35264159, upload-time = "2026-02-23T00:20:23.087Z" }, + { url = "https://files.pythonhosted.org/packages/4d/77/d3ed4becfdbd217c52062fafe35a72388d1bd82c2d0ba5ca19d6fcc93e11/scipy-1.17.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:dbc12c9f3d185f5c737d801da555fb74b3dcfa1a50b66a1a93e09190f41fab50", size = 35102771, upload-time = "2026-02-23T00:20:28.636Z" }, + { url = "https://files.pythonhosted.org/packages/bd/12/d19da97efde68ca1ee5538bb261d5d2c062f0c055575128f11a2730e3ac1/scipy-1.17.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:94055a11dfebe37c656e70317e1996dc197e1a15bbcc351bcdd4610e128fe1ca", size = 37665910, upload-time = "2026-02-23T00:20:34.743Z" }, + { url = "https://files.pythonhosted.org/packages/06/1c/1172a88d507a4baaf72c5a09bb6c018fe2ae0ab622e5830b703a46cc9e44/scipy-1.17.1-cp313-cp313t-win_amd64.whl", hash = "sha256:e30bdeaa5deed6bc27b4cc490823cd0347d7dae09119b8803ae576ea0ce52e4c", size = 36562980, upload-time = "2026-02-23T00:20:40.575Z" }, + { url = "https://files.pythonhosted.org/packages/70/b0/eb757336e5a76dfa7911f63252e3b7d1de00935d7705cf772db5b45ec238/scipy-1.17.1-cp313-cp313t-win_arm64.whl", hash = "sha256:a720477885a9d2411f94a93d16f9d89bad0f28ca23c3f8daa521e2dcc3f44d49", size = 24856543, upload-time = "2026-02-23T00:20:45.313Z" }, + { url = "https://files.pythonhosted.org/packages/cf/83/333afb452af6f0fd70414dc04f898647ee1423979ce02efa75c3b0f2c28e/scipy-1.17.1-cp314-cp314-macosx_10_14_x86_64.whl", hash = "sha256:a48a72c77a310327f6a3a920092fa2b8fd03d7deaa60f093038f22d98e096717", size = 31584510, upload-time = "2026-02-23T00:21:01.015Z" }, + { url = "https://files.pythonhosted.org/packages/ed/a6/d05a85fd51daeb2e4ea71d102f15b34fedca8e931af02594193ae4fd25f7/scipy-1.17.1-cp314-cp314-macosx_12_0_arm64.whl", hash = "sha256:45abad819184f07240d8a696117a7aacd39787af9e0b719d00285549ed19a1e9", size = 28170131, upload-time = "2026-02-23T00:21:05.888Z" }, + { url = "https://files.pythonhosted.org/packages/db/7b/8624a203326675d7746a254083a187398090a179335b2e4a20e2ddc46e83/scipy-1.17.1-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:3fd1fcdab3ea951b610dc4cef356d416d5802991e7e32b5254828d342f7b7e0b", size = 20342032, upload-time = "2026-02-23T00:21:09.904Z" }, + { url = "https://files.pythonhosted.org/packages/c9/35/2c342897c00775d688d8ff3987aced3426858fd89d5a0e26e020b660b301/scipy-1.17.1-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:7bdf2da170b67fdf10bca777614b1c7d96ae3ca5794fd9587dce41eb2966e866", size = 22678766, upload-time = "2026-02-23T00:21:14.313Z" }, + { url = "https://files.pythonhosted.org/packages/ef/f2/7cdb8eb308a1a6ae1e19f945913c82c23c0c442a462a46480ce487fdc0ac/scipy-1.17.1-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:adb2642e060a6549c343603a3851ba76ef0b74cc8c079a9a58121c7ec9fe2350", size = 32957007, upload-time = "2026-02-23T00:21:19.663Z" }, + { url = "https://files.pythonhosted.org/packages/0b/2e/7eea398450457ecb54e18e9d10110993fa65561c4f3add5e8eccd2b9cd41/scipy-1.17.1-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:eee2cfda04c00a857206a4330f0c5e3e56535494e30ca445eb19ec624ae75118", size = 35221333, upload-time = "2026-02-23T00:21:25.278Z" }, + { url = "https://files.pythonhosted.org/packages/d9/77/5b8509d03b77f093a0d52e606d3c4f79e8b06d1d38c441dacb1e26cacf46/scipy-1.17.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:d2650c1fb97e184d12d8ba010493ee7b322864f7d3d00d3f9bb97d9c21de4068", size = 35042066, upload-time = "2026-02-23T00:21:31.358Z" }, + { url = "https://files.pythonhosted.org/packages/f9/df/18f80fb99df40b4070328d5ae5c596f2f00fffb50167e31439e932f29e7d/scipy-1.17.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:08b900519463543aa604a06bec02461558a6e1cef8fdbb8098f77a48a83c8118", size = 37612763, upload-time = "2026-02-23T00:21:37.247Z" }, + { url = "https://files.pythonhosted.org/packages/4b/39/f0e8ea762a764a9dc52aa7dabcfad51a354819de1f0d4652b6a1122424d6/scipy-1.17.1-cp314-cp314-win_amd64.whl", hash = "sha256:3877ac408e14da24a6196de0ddcace62092bfc12a83823e92e49e40747e52c19", size = 37290984, upload-time = "2026-02-23T00:22:35.023Z" }, + { url = "https://files.pythonhosted.org/packages/7c/56/fe201e3b0f93d1a8bcf75d3379affd228a63d7e2d80ab45467a74b494947/scipy-1.17.1-cp314-cp314-win_arm64.whl", hash = "sha256:f8885db0bc2bffa59d5c1b72fad7a6a92d3e80e7257f967dd81abb553a90d293", size = 25192877, upload-time = "2026-02-23T00:22:39.798Z" }, + { url = "https://files.pythonhosted.org/packages/96/ad/f8c414e121f82e02d76f310f16db9899c4fcde36710329502a6b2a3c0392/scipy-1.17.1-cp314-cp314t-macosx_10_14_x86_64.whl", hash = "sha256:1cc682cea2ae55524432f3cdff9e9a3be743d52a7443d0cba9017c23c87ae2f6", size = 31949750, upload-time = "2026-02-23T00:21:42.289Z" }, + { url = "https://files.pythonhosted.org/packages/7c/b0/c741e8865d61b67c81e255f4f0a832846c064e426636cd7de84e74d209be/scipy-1.17.1-cp314-cp314t-macosx_12_0_arm64.whl", hash = "sha256:2040ad4d1795a0ae89bfc7e8429677f365d45aa9fd5e4587cf1ea737f927b4a1", size = 28585858, upload-time = "2026-02-23T00:21:47.706Z" }, + { url = "https://files.pythonhosted.org/packages/ed/1b/3985219c6177866628fa7c2595bfd23f193ceebbe472c98a08824b9466ff/scipy-1.17.1-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:131f5aaea57602008f9822e2115029b55d4b5f7c070287699fe45c661d051e39", size = 20757723, upload-time = "2026-02-23T00:21:52.039Z" }, + { url = "https://files.pythonhosted.org/packages/c0/19/2a04aa25050d656d6f7b9e7b685cc83d6957fb101665bfd9369ca6534563/scipy-1.17.1-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:9cdc1a2fcfd5c52cfb3045feb399f7b3ce822abdde3a193a6b9a60b3cb5854ca", size = 23043098, upload-time = "2026-02-23T00:21:56.185Z" }, + { url = "https://files.pythonhosted.org/packages/86/f1/3383beb9b5d0dbddd030335bf8a8b32d4317185efe495374f134d8be6cce/scipy-1.17.1-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6e3dcd57ab780c741fde8dc68619de988b966db759a3c3152e8e9142c26295ad", size = 33030397, upload-time = "2026-02-23T00:22:01.404Z" }, + { url = "https://files.pythonhosted.org/packages/41/68/8f21e8a65a5a03f25a79165ec9d2b28c00e66dc80546cf5eb803aeeff35b/scipy-1.17.1-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a9956e4d4f4a301ebf6cde39850333a6b6110799d470dbbb1e25326ac447f52a", size = 35281163, upload-time = "2026-02-23T00:22:07.024Z" }, + { url = "https://files.pythonhosted.org/packages/84/8d/c8a5e19479554007a5632ed7529e665c315ae7492b4f946b0deb39870e39/scipy-1.17.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:a4328d245944d09fd639771de275701ccadf5f781ba0ff092ad141e017eccda4", size = 35116291, upload-time = "2026-02-23T00:22:12.585Z" }, + { url = "https://files.pythonhosted.org/packages/52/52/e57eceff0e342a1f50e274264ed47497b59e6a4e3118808ee58ddda7b74a/scipy-1.17.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:a77cbd07b940d326d39a1d1b37817e2ee4d79cb30e7338f3d0cddffae70fcaa2", size = 37682317, upload-time = "2026-02-23T00:22:18.513Z" }, + { url = "https://files.pythonhosted.org/packages/11/2f/b29eafe4a3fbc3d6de9662b36e028d5f039e72d345e05c250e121a230dd4/scipy-1.17.1-cp314-cp314t-win_amd64.whl", hash = "sha256:eb092099205ef62cd1782b006658db09e2fed75bffcae7cc0d44052d8aa0f484", size = 37345327, upload-time = "2026-02-23T00:22:24.442Z" }, + { url = "https://files.pythonhosted.org/packages/07/39/338d9219c4e87f3e708f18857ecd24d22a0c3094752393319553096b98af/scipy-1.17.1-cp314-cp314t-win_arm64.whl", hash = "sha256:200e1050faffacc162be6a486a984a0497866ec54149a01270adc8a59b7c7d21", size = 25489165, upload-time = "2026-02-23T00:22:29.563Z" }, +] + +[[package]] +name = "scipy" +version = "1.18.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.15' and sys_platform == 'win32'", + "python_full_version == '3.14.*' and sys_platform == 'win32'", + "python_full_version >= '3.15' and sys_platform == 'emscripten'", + "python_full_version == '3.14.*' and sys_platform == 'emscripten'", + "python_full_version >= '3.15' and sys_platform != 'darwin' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.14.*' and sys_platform != 'darwin' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version >= '3.15' and sys_platform == 'darwin'", + "python_full_version == '3.14.*' and sys_platform == 'darwin'", + "python_full_version == '3.13.*' and sys_platform == 'win32'", + "python_full_version == '3.12.*' and sys_platform == 'win32'", + "python_full_version == '3.13.*' and sys_platform == 'emscripten'", + "python_full_version == '3.12.*' and sys_platform == 'emscripten'", + "python_full_version == '3.13.*' and sys_platform != 'darwin' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.12.*' and sys_platform != 'darwin' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.13.*' and sys_platform == 'darwin'", + "python_full_version == '3.12.*' and sys_platform == 'darwin'", ] -sdist = { url = "https://files.pythonhosted.org/packages/7a/97/5a3609c4f8d58b039179648e62dd220f89864f56f7357f5d4f45c29eb2cc/scipy-1.17.1.tar.gz", hash = "sha256:95d8e012d8cb8816c226aef832200b1d45109ed4464303e997c5b13122b297c0", size = 30573822 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/df/75/b4ce781849931fef6fd529afa6b63711d5a733065722d0c3e2724af9e40a/scipy-1.17.1-cp311-cp311-macosx_10_14_x86_64.whl", hash = "sha256:1f95b894f13729334fb990162e911c9e5dc1ab390c58aa6cbecb389c5b5e28ec", size = 31613675 }, - { url = "https://files.pythonhosted.org/packages/f7/58/bccc2861b305abdd1b8663d6130c0b3d7cc22e8d86663edbc8401bfd40d4/scipy-1.17.1-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:e18f12c6b0bc5a592ed23d3f7b891f68fd7f8241d69b7883769eb5d5dfb52696", size = 28162057 }, - { url = "https://files.pythonhosted.org/packages/6d/ee/18146b7757ed4976276b9c9819108adbc73c5aad636e5353e20746b73069/scipy-1.17.1-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:a3472cfbca0a54177d0faa68f697d8ba4c80bbdc19908c3465556d9f7efce9ee", size = 20334032 }, - { url = "https://files.pythonhosted.org/packages/ec/e6/cef1cf3557f0c54954198554a10016b6a03b2ec9e22a4e1df734936bd99c/scipy-1.17.1-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:766e0dc5a616d026a3a1cffa379af959671729083882f50307e18175797b3dfd", size = 22709533 }, - { url = "https://files.pythonhosted.org/packages/4d/60/8804678875fc59362b0fb759ab3ecce1f09c10a735680318ac30da8cd76b/scipy-1.17.1-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:744b2bf3640d907b79f3fd7874efe432d1cf171ee721243e350f55234b4cec4c", size = 33062057 }, - { url = "https://files.pythonhosted.org/packages/09/7d/af933f0f6e0767995b4e2d705a0665e454d1c19402aa7e895de3951ebb04/scipy-1.17.1-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:43af8d1f3bea642559019edfe64e9b11192a8978efbd1539d7bc2aaa23d92de4", size = 35349300 }, - { url = "https://files.pythonhosted.org/packages/b4/3d/7ccbbdcbb54c8fdc20d3b6930137c782a163fa626f0aef920349873421ba/scipy-1.17.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:cd96a1898c0a47be4520327e01f874acfd61fb48a9420f8aa9f6483412ffa444", size = 35127333 }, - { url = "https://files.pythonhosted.org/packages/e8/19/f926cb11c42b15ba08e3a71e376d816ac08614f769b4f47e06c3580c836a/scipy-1.17.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:4eb6c25dd62ee8d5edf68a8e1c171dd71c292fdae95d8aeb3dd7d7de4c364082", size = 37741314 }, - { url = "https://files.pythonhosted.org/packages/95/da/0d1df507cf574b3f224ccc3d45244c9a1d732c81dcb26b1e8a766ae271a8/scipy-1.17.1-cp311-cp311-win_amd64.whl", hash = "sha256:d30e57c72013c2a4fe441c2fcb8e77b14e152ad48b5464858e07e2ad9fbfceff", size = 36607512 }, - { url = "https://files.pythonhosted.org/packages/68/7f/bdd79ceaad24b671543ffe0ef61ed8e659440eb683b66f033454dcee90eb/scipy-1.17.1-cp311-cp311-win_arm64.whl", hash = "sha256:9ecb4efb1cd6e8c4afea0daa91a87fbddbce1b99d2895d151596716c0b2e859d", size = 24599248 }, - { url = "https://files.pythonhosted.org/packages/35/48/b992b488d6f299dbe3f11a20b24d3dda3d46f1a635ede1c46b5b17a7b163/scipy-1.17.1-cp312-cp312-macosx_10_14_x86_64.whl", hash = "sha256:35c3a56d2ef83efc372eaec584314bd0ef2e2f0d2adb21c55e6ad5b344c0dcb8", size = 31610954 }, - { url = "https://files.pythonhosted.org/packages/b2/02/cf107b01494c19dc100f1d0b7ac3cc08666e96ba2d64db7626066cee895e/scipy-1.17.1-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:fcb310ddb270a06114bb64bbe53c94926b943f5b7f0842194d585c65eb4edd76", size = 28172662 }, - { url = "https://files.pythonhosted.org/packages/cf/a9/599c28631bad314d219cf9ffd40e985b24d603fc8a2f4ccc5ae8419a535b/scipy-1.17.1-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:cc90d2e9c7e5c7f1a482c9875007c095c3194b1cfedca3c2f3291cdc2bc7c086", size = 20344366 }, - { url = "https://files.pythonhosted.org/packages/35/f5/906eda513271c8deb5af284e5ef0206d17a96239af79f9fa0aebfe0e36b4/scipy-1.17.1-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:c80be5ede8f3f8eded4eff73cc99a25c388ce98e555b17d31da05287015ffa5b", size = 22704017 }, - { url = "https://files.pythonhosted.org/packages/da/34/16f10e3042d2f1d6b66e0428308ab52224b6a23049cb2f5c1756f713815f/scipy-1.17.1-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e19ebea31758fac5893a2ac360fedd00116cbb7628e650842a6691ba7ca28a21", size = 32927842 }, - { url = "https://files.pythonhosted.org/packages/01/8e/1e35281b8ab6d5d72ebe9911edcdffa3f36b04ed9d51dec6dd140396e220/scipy-1.17.1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:02ae3b274fde71c5e92ac4d54bc06c42d80e399fec704383dcd99b301df37458", size = 35235890 }, - { url = "https://files.pythonhosted.org/packages/c5/5c/9d7f4c88bea6e0d5a4f1bc0506a53a00e9fcb198de372bfe4d3652cef482/scipy-1.17.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8a604bae87c6195d8b1045eddece0514d041604b14f2727bbc2b3020172045eb", size = 35003557 }, - { url = "https://files.pythonhosted.org/packages/65/94/7698add8f276dbab7a9de9fb6b0e02fc13ee61d51c7c3f85ac28b65e1239/scipy-1.17.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:f590cd684941912d10becc07325a3eeb77886fe981415660d9265c4c418d0bea", size = 37625856 }, - { url = "https://files.pythonhosted.org/packages/a2/84/dc08d77fbf3d87d3ee27f6a0c6dcce1de5829a64f2eae85a0ecc1f0daa73/scipy-1.17.1-cp312-cp312-win_amd64.whl", hash = "sha256:41b71f4a3a4cab9d366cd9065b288efc4d4f3c0b37a91a8e0947fb5bd7f31d87", size = 36549682 }, - { url = "https://files.pythonhosted.org/packages/bc/98/fe9ae9ffb3b54b62559f52dedaebe204b408db8109a8c66fdd04869e6424/scipy-1.17.1-cp312-cp312-win_arm64.whl", hash = "sha256:f4115102802df98b2b0db3cce5cb9b92572633a1197c77b7553e5203f284a5b3", size = 24547340 }, - { url = "https://files.pythonhosted.org/packages/76/27/07ee1b57b65e92645f219b37148a7e7928b82e2b5dbeccecb4dff7c64f0b/scipy-1.17.1-cp313-cp313-macosx_10_14_x86_64.whl", hash = "sha256:5e3c5c011904115f88a39308379c17f91546f77c1667cea98739fe0fccea804c", size = 31590199 }, - { url = "https://files.pythonhosted.org/packages/ec/ae/db19f8ab842e9b724bf5dbb7db29302a91f1e55bc4d04b1025d6d605a2c5/scipy-1.17.1-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:6fac755ca3d2c3edcb22f479fceaa241704111414831ddd3bc6056e18516892f", size = 28154001 }, - { url = "https://files.pythonhosted.org/packages/5b/58/3ce96251560107b381cbd6e8413c483bbb1228a6b919fa8652b0d4090e7f/scipy-1.17.1-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:7ff200bf9d24f2e4d5dc6ee8c3ac64d739d3a89e2326ba68aaf6c4a2b838fd7d", size = 20325719 }, - { url = "https://files.pythonhosted.org/packages/b2/83/15087d945e0e4d48ce2377498abf5ad171ae013232ae31d06f336e64c999/scipy-1.17.1-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:4b400bdc6f79fa02a4d86640310dde87a21fba0c979efff5248908c6f15fad1b", size = 22683595 }, - { url = "https://files.pythonhosted.org/packages/b4/e0/e58fbde4a1a594c8be8114eb4aac1a55bcd6587047efc18a61eb1f5c0d30/scipy-1.17.1-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2b64ca7d4aee0102a97f3ba22124052b4bd2152522355073580bf4845e2550b6", size = 32896429 }, - { url = "https://files.pythonhosted.org/packages/f5/5f/f17563f28ff03c7b6799c50d01d5d856a1d55f2676f537ca8d28c7f627cd/scipy-1.17.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:581b2264fc0aa555f3f435a5944da7504ea3a065d7029ad60e7c3d1ae09c5464", size = 35203952 }, - { url = "https://files.pythonhosted.org/packages/8d/a5/9afd17de24f657fdfe4df9a3f1ea049b39aef7c06000c13db1530d81ccca/scipy-1.17.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:beeda3d4ae615106d7094f7e7cef6218392e4465cc95d25f900bebabfded0950", size = 34979063 }, - { url = "https://files.pythonhosted.org/packages/8b/13/88b1d2384b424bf7c924f2038c1c409f8d88bb2a8d49d097861dd64a57b2/scipy-1.17.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6609bc224e9568f65064cfa72edc0f24ee6655b47575954ec6339534b2798369", size = 37598449 }, - { url = "https://files.pythonhosted.org/packages/35/e5/d6d0e51fc888f692a35134336866341c08655d92614f492c6860dc45bb2c/scipy-1.17.1-cp313-cp313-win_amd64.whl", hash = "sha256:37425bc9175607b0268f493d79a292c39f9d001a357bebb6b88fdfaff13f6448", size = 36510943 }, - { url = "https://files.pythonhosted.org/packages/2a/fd/3be73c564e2a01e690e19cc618811540ba5354c67c8680dce3281123fb79/scipy-1.17.1-cp313-cp313-win_arm64.whl", hash = "sha256:5cf36e801231b6a2059bf354720274b7558746f3b1a4efb43fcf557ccd484a87", size = 24545621 }, - { url = "https://files.pythonhosted.org/packages/6f/6b/17787db8b8114933a66f9dcc479a8272e4b4da75fe03b0c282f7b0ade8cd/scipy-1.17.1-cp313-cp313t-macosx_10_14_x86_64.whl", hash = "sha256:d59c30000a16d8edc7e64152e30220bfbd724c9bbb08368c054e24c651314f0a", size = 31936708 }, - { url = "https://files.pythonhosted.org/packages/38/2e/524405c2b6392765ab1e2b722a41d5da33dc5c7b7278184a8ad29b6cb206/scipy-1.17.1-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:010f4333c96c9bb1a4516269e33cb5917b08ef2166d5556ca2fd9f082a9e6ea0", size = 28570135 }, - { url = "https://files.pythonhosted.org/packages/fd/c3/5bd7199f4ea8556c0c8e39f04ccb014ac37d1468e6cfa6a95c6b3562b76e/scipy-1.17.1-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:2ceb2d3e01c5f1d83c4189737a42d9cb2fc38a6eeed225e7515eef71ad301dce", size = 20741977 }, - { url = "https://files.pythonhosted.org/packages/d9/b8/8ccd9b766ad14c78386599708eb745f6b44f08400a5fd0ade7cf89b6fc93/scipy-1.17.1-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:844e165636711ef41f80b4103ed234181646b98a53c8f05da12ca5ca289134f6", size = 23029601 }, - { url = "https://files.pythonhosted.org/packages/6d/a0/3cb6f4d2fb3e17428ad2880333cac878909ad1a89f678527b5328b93c1d4/scipy-1.17.1-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:158dd96d2207e21c966063e1635b1063cd7787b627b6f07305315dd73d9c679e", size = 33019667 }, - { url = "https://files.pythonhosted.org/packages/f3/c3/2d834a5ac7bf3a0c806ad1508efc02dda3c8c61472a56132d7894c312dea/scipy-1.17.1-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:74cbb80d93260fe2ffa334efa24cb8f2f0f622a9b9febf8b483c0b865bfb3475", size = 35264159 }, - { url = "https://files.pythonhosted.org/packages/4d/77/d3ed4becfdbd217c52062fafe35a72388d1bd82c2d0ba5ca19d6fcc93e11/scipy-1.17.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:dbc12c9f3d185f5c737d801da555fb74b3dcfa1a50b66a1a93e09190f41fab50", size = 35102771 }, - { url = "https://files.pythonhosted.org/packages/bd/12/d19da97efde68ca1ee5538bb261d5d2c062f0c055575128f11a2730e3ac1/scipy-1.17.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:94055a11dfebe37c656e70317e1996dc197e1a15bbcc351bcdd4610e128fe1ca", size = 37665910 }, - { url = "https://files.pythonhosted.org/packages/06/1c/1172a88d507a4baaf72c5a09bb6c018fe2ae0ab622e5830b703a46cc9e44/scipy-1.17.1-cp313-cp313t-win_amd64.whl", hash = "sha256:e30bdeaa5deed6bc27b4cc490823cd0347d7dae09119b8803ae576ea0ce52e4c", size = 36562980 }, - { url = "https://files.pythonhosted.org/packages/70/b0/eb757336e5a76dfa7911f63252e3b7d1de00935d7705cf772db5b45ec238/scipy-1.17.1-cp313-cp313t-win_arm64.whl", hash = "sha256:a720477885a9d2411f94a93d16f9d89bad0f28ca23c3f8daa521e2dcc3f44d49", size = 24856543 }, - { url = "https://files.pythonhosted.org/packages/cf/83/333afb452af6f0fd70414dc04f898647ee1423979ce02efa75c3b0f2c28e/scipy-1.17.1-cp314-cp314-macosx_10_14_x86_64.whl", hash = "sha256:a48a72c77a310327f6a3a920092fa2b8fd03d7deaa60f093038f22d98e096717", size = 31584510 }, - { url = "https://files.pythonhosted.org/packages/ed/a6/d05a85fd51daeb2e4ea71d102f15b34fedca8e931af02594193ae4fd25f7/scipy-1.17.1-cp314-cp314-macosx_12_0_arm64.whl", hash = "sha256:45abad819184f07240d8a696117a7aacd39787af9e0b719d00285549ed19a1e9", size = 28170131 }, - { url = "https://files.pythonhosted.org/packages/db/7b/8624a203326675d7746a254083a187398090a179335b2e4a20e2ddc46e83/scipy-1.17.1-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:3fd1fcdab3ea951b610dc4cef356d416d5802991e7e32b5254828d342f7b7e0b", size = 20342032 }, - { url = "https://files.pythonhosted.org/packages/c9/35/2c342897c00775d688d8ff3987aced3426858fd89d5a0e26e020b660b301/scipy-1.17.1-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:7bdf2da170b67fdf10bca777614b1c7d96ae3ca5794fd9587dce41eb2966e866", size = 22678766 }, - { url = "https://files.pythonhosted.org/packages/ef/f2/7cdb8eb308a1a6ae1e19f945913c82c23c0c442a462a46480ce487fdc0ac/scipy-1.17.1-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:adb2642e060a6549c343603a3851ba76ef0b74cc8c079a9a58121c7ec9fe2350", size = 32957007 }, - { url = "https://files.pythonhosted.org/packages/0b/2e/7eea398450457ecb54e18e9d10110993fa65561c4f3add5e8eccd2b9cd41/scipy-1.17.1-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:eee2cfda04c00a857206a4330f0c5e3e56535494e30ca445eb19ec624ae75118", size = 35221333 }, - { url = "https://files.pythonhosted.org/packages/d9/77/5b8509d03b77f093a0d52e606d3c4f79e8b06d1d38c441dacb1e26cacf46/scipy-1.17.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:d2650c1fb97e184d12d8ba010493ee7b322864f7d3d00d3f9bb97d9c21de4068", size = 35042066 }, - { url = "https://files.pythonhosted.org/packages/f9/df/18f80fb99df40b4070328d5ae5c596f2f00fffb50167e31439e932f29e7d/scipy-1.17.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:08b900519463543aa604a06bec02461558a6e1cef8fdbb8098f77a48a83c8118", size = 37612763 }, - { url = "https://files.pythonhosted.org/packages/4b/39/f0e8ea762a764a9dc52aa7dabcfad51a354819de1f0d4652b6a1122424d6/scipy-1.17.1-cp314-cp314-win_amd64.whl", hash = "sha256:3877ac408e14da24a6196de0ddcace62092bfc12a83823e92e49e40747e52c19", size = 37290984 }, - { url = "https://files.pythonhosted.org/packages/7c/56/fe201e3b0f93d1a8bcf75d3379affd228a63d7e2d80ab45467a74b494947/scipy-1.17.1-cp314-cp314-win_arm64.whl", hash = "sha256:f8885db0bc2bffa59d5c1b72fad7a6a92d3e80e7257f967dd81abb553a90d293", size = 25192877 }, - { url = "https://files.pythonhosted.org/packages/96/ad/f8c414e121f82e02d76f310f16db9899c4fcde36710329502a6b2a3c0392/scipy-1.17.1-cp314-cp314t-macosx_10_14_x86_64.whl", hash = "sha256:1cc682cea2ae55524432f3cdff9e9a3be743d52a7443d0cba9017c23c87ae2f6", size = 31949750 }, - { url = "https://files.pythonhosted.org/packages/7c/b0/c741e8865d61b67c81e255f4f0a832846c064e426636cd7de84e74d209be/scipy-1.17.1-cp314-cp314t-macosx_12_0_arm64.whl", hash = "sha256:2040ad4d1795a0ae89bfc7e8429677f365d45aa9fd5e4587cf1ea737f927b4a1", size = 28585858 }, - { url = "https://files.pythonhosted.org/packages/ed/1b/3985219c6177866628fa7c2595bfd23f193ceebbe472c98a08824b9466ff/scipy-1.17.1-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:131f5aaea57602008f9822e2115029b55d4b5f7c070287699fe45c661d051e39", size = 20757723 }, - { url = "https://files.pythonhosted.org/packages/c0/19/2a04aa25050d656d6f7b9e7b685cc83d6957fb101665bfd9369ca6534563/scipy-1.17.1-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:9cdc1a2fcfd5c52cfb3045feb399f7b3ce822abdde3a193a6b9a60b3cb5854ca", size = 23043098 }, - { url = "https://files.pythonhosted.org/packages/86/f1/3383beb9b5d0dbddd030335bf8a8b32d4317185efe495374f134d8be6cce/scipy-1.17.1-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6e3dcd57ab780c741fde8dc68619de988b966db759a3c3152e8e9142c26295ad", size = 33030397 }, - { url = "https://files.pythonhosted.org/packages/41/68/8f21e8a65a5a03f25a79165ec9d2b28c00e66dc80546cf5eb803aeeff35b/scipy-1.17.1-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a9956e4d4f4a301ebf6cde39850333a6b6110799d470dbbb1e25326ac447f52a", size = 35281163 }, - { url = "https://files.pythonhosted.org/packages/84/8d/c8a5e19479554007a5632ed7529e665c315ae7492b4f946b0deb39870e39/scipy-1.17.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:a4328d245944d09fd639771de275701ccadf5f781ba0ff092ad141e017eccda4", size = 35116291 }, - { url = "https://files.pythonhosted.org/packages/52/52/e57eceff0e342a1f50e274264ed47497b59e6a4e3118808ee58ddda7b74a/scipy-1.17.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:a77cbd07b940d326d39a1d1b37817e2ee4d79cb30e7338f3d0cddffae70fcaa2", size = 37682317 }, - { url = "https://files.pythonhosted.org/packages/11/2f/b29eafe4a3fbc3d6de9662b36e028d5f039e72d345e05c250e121a230dd4/scipy-1.17.1-cp314-cp314t-win_amd64.whl", hash = "sha256:eb092099205ef62cd1782b006658db09e2fed75bffcae7cc0d44052d8aa0f484", size = 37345327 }, - { url = "https://files.pythonhosted.org/packages/07/39/338d9219c4e87f3e708f18857ecd24d22a0c3094752393319553096b98af/scipy-1.17.1-cp314-cp314t-win_arm64.whl", hash = "sha256:200e1050faffacc162be6a486a984a0497866ec54149a01270adc8a59b7c7d21", size = 25489165 }, +dependencies = [ + { name = "numpy", version = "2.5.1", source = { registry = "https://pypi.org/simple" } }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a7/25/c2700dfaf6442b4effaa91af24ebce5dc9d31bb4a69706313aae70d72cd0/scipy-1.18.0.tar.gz", hash = "sha256:67b2ad2ad54c72ca6d04975a9b2df8c3638c34ddd5b28738e94fc2b57929d378", size = 30774447, upload-time = "2026-06-19T15:01:43.456Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6a/19/ca10ead60b0acc80b2b833c2c4a4f2ff753d0f58b811f70d911c7e94a25c/scipy-1.18.0-cp312-cp312-macosx_10_15_x86_64.whl", hash = "sha256:7bd21faaf5a1a3b2eff922d02db5f191b99a6518db9078a8fb23169f6d22259a", size = 31056519, upload-time = "2026-06-19T14:59:45.203Z" }, + { url = "https://files.pythonhosted.org/packages/96/72/1e6442a00cd2924d361aa1b642ab6373ec35c6fabf311a760be9f76e0f13/scipy-1.18.0-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:265915e79107de9f946b855e50d7470d5893ec3f54b342e1aa6201cbdcd8bb6b", size = 28681889, upload-time = "2026-06-19T14:59:48.103Z" }, + { url = "https://files.pythonhosted.org/packages/9b/2d/11dd93d21e147a73ba22bd75c0b9208d3a2e0ec76d53170ce7d9029b1015/scipy-1.18.0-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:9ab7b758be6940954a713ee466e2043e9f6e2ed965c1fce5c91039f4be3d90a9", size = 20423580, upload-time = "2026-06-19T14:59:50.665Z" }, + { url = "https://files.pythonhosted.org/packages/9c/01/93552f75e0d2a7dd115a45e59209c51e8d514daff02fc887d2623be06fe1/scipy-1.18.0-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:97b6cddaaee0a779ef6b5ca83c9604b27cc16b2b8fc22c142652df8793319fb8", size = 23054441, upload-time = "2026-06-19T14:59:53.564Z" }, + { url = "https://files.pythonhosted.org/packages/3c/23/21f5e703643d66f21faa6b4c73195bfcad70c55efcb4f1ab327cd7c4101a/scipy-1.18.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:52a96e21517c7292375c0e27dd796a811f03fcea5fd4d108fdfea8145dcf17ab", size = 33968720, upload-time = "2026-06-19T14:59:56.415Z" }, + { url = "https://files.pythonhosted.org/packages/dd/aa/1b939f6c67ed68635bb538e6752d3dacc02f66535182e939a89581a44e9c/scipy-1.18.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1f55797419e16e7f30cf88ffb3113ce0467f00cfe3f70d5c281730b21769bfc2", size = 35287115, upload-time = "2026-06-19T14:59:59.411Z" }, + { url = "https://files.pythonhosted.org/packages/b6/ff/eec46be7e9234208f801062b53e1983085eddebd693f6c9bfb03b459830d/scipy-1.18.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ad033410e2e0672ffdc1042110cef20e1c46f8fd0616cee1d44d8d58fad8fc11", size = 35577989, upload-time = "2026-06-19T15:00:02.235Z" }, + { url = "https://files.pythonhosted.org/packages/84/ca/210d4759c7210bb7d269437421959b39a33434e2776b60c5cb8a763bb30a/scipy-1.18.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:4a55985d54c769c872e64b7f4c8a81cc30ef700cc04296abbbf3705439c126de", size = 37421717, upload-time = "2026-06-19T15:00:05.102Z" }, + { url = "https://files.pythonhosted.org/packages/2b/54/9a9edb45345bd6744da5ddfb6628e5d5185920494c6a67ec45b6381004cb/scipy-1.18.0-cp312-cp312-win_amd64.whl", hash = "sha256:71ccc8faa2dd16ac310233203474a8b5cb67f10dedd54a3116d34943f4b19132", size = 36597428, upload-time = "2026-06-19T15:00:08.112Z" }, + { url = "https://files.pythonhosted.org/packages/99/0e/33f32a2a58987e26aec0f7df252cbbad1e90ae77bdbc76f40dd4ed0cf0ea/scipy-1.18.0-cp312-cp312-win_arm64.whl", hash = "sha256:d88363fd9d8fbd3511bd273f1a49efb2a540773ddf92a91d57498ce7dd7f3e76", size = 24351481, upload-time = "2026-06-19T15:00:11.103Z" }, + { url = "https://files.pythonhosted.org/packages/05/52/9c0136c2de7ae0779b7b366447766cec6d9f0702c56bb8ffeb04c8fd3af4/scipy-1.18.0-cp313-cp313-macosx_10_15_x86_64.whl", hash = "sha256:09143f676d157d9f546d663504ef9c1becb819824f1afc018814176411942446", size = 31036107, upload-time = "2026-06-19T15:00:14.03Z" }, + { url = "https://files.pythonhosted.org/packages/02/73/0291a64843270f4efb86cdcf2ee0f2048631b65ec6b405398b2b4dbf11bf/scipy-1.18.0-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:5efe260f69417b97ddae455bfb5a95e8359f7f66ad7fa9522a60feb66f169520", size = 28663303, upload-time = "2026-06-19T15:00:16.819Z" }, + { url = "https://files.pythonhosted.org/packages/d3/0f/10ffa0b697a572f4e0d48b92a88895d366422f019f723e7e14a84c050dac/scipy-1.18.0-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:68363b7eaacd8b5dd426df56d782cc156468ac79a127a1b87ca597d6e2e82197", size = 20404960, upload-time = "2026-06-19T15:00:19.635Z" }, + { url = "https://files.pythonhosted.org/packages/7e/d2/e896cea21ba8edd6c81d4c55b1ffcc717e79698dcbebf9641b4cfb4c6622/scipy-1.18.0-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:c5557d8be5da8e41353fcd4d21491fdbab83b062fc579e94dc09a7c8ab4f669b", size = 23034074, upload-time = "2026-06-19T15:00:22.107Z" }, + { url = "https://files.pythonhosted.org/packages/ea/b2/e83ea34279a52c03374477c74006256ec78df65fc877baa4617d6de1d202/scipy-1.18.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0d13bca67c096d89fb95ced0d8921807300fce0275643aef9533cc63a0773468", size = 33942038, upload-time = "2026-06-19T15:00:24.964Z" }, + { url = "https://files.pythonhosted.org/packages/f6/af/e8fe5fb136f51e2b01678b92cb4106d10d8cd68ec147ead2e7cb0ac75398/scipy-1.18.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a46f9273dbd0eb1cefba61c9b8648b4dfe3cbc14a080176f9a73e44b8336dc7f", size = 35266390, upload-time = "2026-06-19T15:00:28.059Z" }, + { url = "https://files.pythonhosted.org/packages/3a/49/2c5cbb907b56695fc67517811d1db234dfd83381a84814ec220aded2794d/scipy-1.18.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5aba46108853ddfc77906b6557aac839d2b52e900c1d72a1180adaaab58d265f", size = 35551324, upload-time = "2026-06-19T15:00:31.014Z" }, + { url = "https://files.pythonhosted.org/packages/bb/73/eda39f7a2d306ff0ffc574afd13c0bbb6d10a603d9a413998ee269487a80/scipy-1.18.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b6f758e35f12757b5d95c00bc6de2438e229c2664b7a92e96f205959d9f2dfa4", size = 37404785, upload-time = "2026-06-19T15:00:34.072Z" }, + { url = "https://files.pythonhosted.org/packages/b7/d2/ae881ee28d014f38e0ccbfd974a06a919ba9af34f1f74bf42b5301891d63/scipy-1.18.0-cp313-cp313-win_amd64.whl", hash = "sha256:1afac4a847207c7ff8efd321734a50b06d0280b3b2a2c0fc2f413101747ad7c7", size = 36554943, upload-time = "2026-06-19T15:00:36.903Z" }, + { url = "https://files.pythonhosted.org/packages/70/3a/21154e2d54eb3639c6bf4dbae2e531c68356bfe95990daa30df33b30d556/scipy-1.18.0-cp313-cp313-win_arm64.whl", hash = "sha256:c5dbddf60e58c2312316d097271a8e73d40eaf2eabfa4d95ed7d3695bbf2ce7b", size = 24350911, upload-time = "2026-06-19T15:00:40.062Z" }, + { url = "https://files.pythonhosted.org/packages/78/b5/915a19b3de2f7430062b509653563db1633ddbb6f021b06731521115d4e2/scipy-1.18.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:4c256ee70c0d1a8a2ace807e199ccd4e3f57037433842abb3fb36bc17eaa9578", size = 31036253, upload-time = "2026-06-19T15:00:43.216Z" }, + { url = "https://files.pythonhosted.org/packages/d7/88/b72def7262e150d16be13fca37a96481138d624e700340bc3362a7588929/scipy-1.18.0-cp314-cp314-macosx_12_0_arm64.whl", hash = "sha256:2ef3abc54a4ffc53765374b0d5728532dfdd2585ed23f6b11c206a1f0b1b9af8", size = 28673758, upload-time = "2026-06-19T15:00:46.663Z" }, + { url = "https://files.pythonhosted.org/packages/91/02/2e636a61a525632c373cf6a9c24442a3ffb79e364d38e98b32042964ac32/scipy-1.18.0-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:f2a6af57bd9e4a75d70e4117e78a1bbee84f79ae3fbb6d0111005d6ebcc4cb8d", size = 20415514, upload-time = "2026-06-19T15:00:49.399Z" }, + { url = "https://files.pythonhosted.org/packages/c9/b6/2135974442f6aba159d9d39d774a1c8cb19947016725d69fecc685df45bf/scipy-1.18.0-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:3f1ac564d3bf6c03d861d2cd87a1bea0da2887136f7fb1bf519c05a8971452d6", size = 23034398, upload-time = "2026-06-19T15:00:51.941Z" }, + { url = "https://files.pythonhosted.org/packages/f6/e6/ba89ec5abf6ee9257c0d1ec985573f3ae32742c24bc03e016388a40b1b15/scipy-1.18.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:40395a5fcd1abee49a5c7aaa98c29db393eedc835138560a588c47ec16156690", size = 33998032, upload-time = "2026-06-19T15:00:54.838Z" }, + { url = "https://files.pythonhosted.org/packages/7f/c4/bc41eb19b0fd0db868f4132920879019318d80cc522ad8f2bca4611af808/scipy-1.18.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8ca01e8ae69f1b18e9a58d91afead31be3cef0dd905a10249dac559ee15460a0", size = 35283333, upload-time = "2026-06-19T15:00:58.152Z" }, + { url = "https://files.pythonhosted.org/packages/53/a4/cbdeef6eb3830a8462a9d4ada814de5fc984345cc9ecf17cbec51a036f1e/scipy-1.18.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7a7f3b01647384dbc3a711e8c6778e0aabbe93959249fef5c7393396bcac0867", size = 35610216, upload-time = "2026-06-19T15:01:01.155Z" }, + { url = "https://files.pythonhosted.org/packages/80/4d/b2b82502b65f661d1b789c1665dcdf315d5f12194e06fc0b37946294ebae/scipy-1.18.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:6aa94e78ec192a30063a5e72e561c28af769dc311190b24fe91774eff1969709", size = 37418960, upload-time = "2026-06-19T15:01:04.155Z" }, + { url = "https://files.pythonhosted.org/packages/93/3e/902d836831474b0ab5a37d16404f7bc5fafd9efba632890e271ba952635f/scipy-1.18.0-cp314-cp314-win_amd64.whl", hash = "sha256:2d8bbdc6c817f5b4006a54d799d4f5bab6f910193cbb9a1ff310833d4d270f61", size = 37288845, upload-time = "2026-06-19T15:01:07.822Z" }, + { url = "https://files.pythonhosted.org/packages/b6/43/8d73b337a3bdb14daa0314f0434210747c02d79d729ce1777574a817dcf6/scipy-1.18.0-cp314-cp314-win_arm64.whl", hash = "sha256:18e9575f1569b2c54174e6159d32942e03731177f63dce7975f0a0c88d102f5b", size = 24988971, upload-time = "2026-06-19T15:01:11.076Z" }, + { url = "https://files.pythonhosted.org/packages/b4/b4/f11918b0508a2787031a0499a03fbe3546f3bb5ca05d01038c45b278c09a/scipy-1.18.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:f351e0dd702687d12a402b867a1b4146a256923e1c38317cbc472f6372b94707", size = 31399325, upload-time = "2026-06-19T15:01:13.723Z" }, + { url = "https://files.pythonhosted.org/packages/7b/d1/1f287b57c0ff0ee5185dff3946d92c8017d39b0e431f0ae79a3ff1859512/scipy-1.18.0-cp314-cp314t-macosx_12_0_arm64.whl", hash = "sha256:7c7a51b33ce387193c97f228320cf8e87361daa1bba750638677729598b3e677", size = 29092110, upload-time = "2026-06-19T15:01:16.908Z" }, + { url = "https://files.pythonhosted.org/packages/ff/1a/7b74eb6c392fdcb27d414c0e7558a6d0231eb3b6d73571f479bb81ea8794/scipy-1.18.0-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:84031d7b052a54fae2f8632e0ec802073d385476eb9a63079bce6e23ef9283d4", size = 20833811, upload-time = "2026-06-19T15:01:20.488Z" }, + { url = "https://files.pythonhosted.org/packages/7c/ad/f3941716320a7b9cb4d68734a903b45fe16eff5fb7da7e16f2e619304979/scipy-1.18.0-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:56abf29a7c067dde59be8b9a22d606a4ea1b2f2a4b756d9d903c62818f5dacce", size = 23396644, upload-time = "2026-06-19T15:01:23.364Z" }, + { url = "https://files.pythonhosted.org/packages/22/22/1446b62ffe07f9719b7d9b1b6a4e05a772833ae8f441fe4c22c34c9b250f/scipy-1.18.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1ad44305cfa24b1ba5803cbbebf033590ccbac1aa5d612d727b785325ab408b0", size = 34079318, upload-time = "2026-06-19T15:01:26.002Z" }, + { url = "https://files.pythonhosted.org/packages/56/3b/b87da667098bb470fa30c7011b0ba351ee976dd395c78798c66e941665a3/scipy-1.18.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:945c1761b93f38d7f99ae81ae80c63e621471608c7eeead563f6df025585cd58", size = 35324320, upload-time = "2026-06-19T15:01:28.881Z" }, + { url = "https://files.pythonhosted.org/packages/f8/a1/c7932f91909759b0267f75fdea34e91309f96b895757534b76a90b6b4344/scipy-1.18.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:1a4441f15d620578772a49e5ab48c0ee1f7a0220e387110283062729136b2553", size = 35699541, upload-time = "2026-06-19T15:01:31.968Z" }, + { url = "https://files.pythonhosted.org/packages/f7/86/5185061a1fcc41d18c5dc2463969b3a3964b31d9ac67b2fb05d4c7ff7670/scipy-1.18.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:9aac6192fac56bf2ca534389d24623f07b39ff83317d58287285e7fbd622ff76", size = 37472480, upload-time = "2026-06-19T15:01:35.136Z" }, + { url = "https://files.pythonhosted.org/packages/31/8e/f04c68e39919a010d34f2ee1367fd705b0a25a02f609d755f0bfbc0a15fc/scipy-1.18.0-cp314-cp314t-win_amd64.whl", hash = "sha256:e40baea28ae7f5475c779741e2d90b1247c78531207b49c7030e698ff81cee3f", size = 37365390, upload-time = "2026-06-19T15:01:38.091Z" }, + { url = "https://files.pythonhosted.org/packages/d5/19/969dc072906c84dd0a3b05dcf57ea750936087d7873549e408b35cfc3f97/scipy-1.18.0-cp314-cp314t-win_arm64.whl", hash = "sha256:368e0a705903c466aa5f08eefb39e6b1b6b2d659e7352a31fd9e2438365be0f8", size = 25279661, upload-time = "2026-06-19T15:01:40.817Z" }, ] [[package]] @@ -1881,30 +2465,31 @@ version = "0.13.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "matplotlib" }, - { name = "numpy" }, + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, + { name = "numpy", version = "2.5.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, { name = "pandas" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/86/59/a451d7420a77ab0b98f7affa3a1d78a313d2f7281a57afb1a34bae8ab412/seaborn-0.13.2.tar.gz", hash = "sha256:93e60a40988f4d65e9f4885df477e2fdaff6b73a9ded434c1ab356dd57eefff7", size = 1457696 } +sdist = { url = "https://files.pythonhosted.org/packages/86/59/a451d7420a77ab0b98f7affa3a1d78a313d2f7281a57afb1a34bae8ab412/seaborn-0.13.2.tar.gz", hash = "sha256:93e60a40988f4d65e9f4885df477e2fdaff6b73a9ded434c1ab356dd57eefff7", size = 1457696, upload-time = "2024-01-25T13:21:52.551Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/83/11/00d3c3dfc25ad54e731d91449895a79e4bf2384dc3ac01809010ba88f6d5/seaborn-0.13.2-py3-none-any.whl", hash = "sha256:636f8336facf092165e27924f223d3c62ca560b1f2bb5dff7ab7fad265361987", size = 294914 }, + { url = "https://files.pythonhosted.org/packages/83/11/00d3c3dfc25ad54e731d91449895a79e4bf2384dc3ac01809010ba88f6d5/seaborn-0.13.2-py3-none-any.whl", hash = "sha256:636f8336facf092165e27924f223d3c62ca560b1f2bb5dff7ab7fad265361987", size = 294914, upload-time = "2024-01-25T13:21:49.598Z" }, ] [[package]] name = "setuptools" -version = "81.0.0" +version = "83.0.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/0d/1c/73e719955c59b8e424d015ab450f51c0af856ae46ea2da83eba51cc88de1/setuptools-81.0.0.tar.gz", hash = "sha256:487b53915f52501f0a79ccfd0c02c165ffe06631443a886740b91af4b7a5845a", size = 1198299 } +sdist = { url = "https://files.pythonhosted.org/packages/34/26/f5d29e25ffdb535afef2d35cdb55b325298f96debd670da4c325e08d70f4/setuptools-83.0.0.tar.gz", hash = "sha256:025bccbbf0fa05b6192bc64ae1e7b16e001fd6d6d4d5de03c97b1c1ade523bef", size = 1154254, upload-time = "2026-07-04T15:31:22.699Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e1/e3/c164c88b2e5ce7b24d667b9bd83589cf4f3520d97cad01534cd3c4f55fdb/setuptools-81.0.0-py3-none-any.whl", hash = "sha256:fdd925d5c5d9f62e4b74b30d6dd7828ce236fd6ed998a08d81de62ce5a6310d6", size = 1062021 }, + { url = "https://files.pythonhosted.org/packages/5d/40/e1e72872c6354b306daef1703549e8e83b4d43cfea356311bf722a043752/setuptools-83.0.0-py3-none-any.whl", hash = "sha256:29b23c360f22f414dc7336bb39178cc7bcbf6021ed2733cde173f09dba19abb3", size = 1008090, upload-time = "2026-07-04T15:31:20.885Z" }, ] [[package]] name = "shellingham" version = "1.5.4" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/58/15/8b3609fd3830ef7b27b655beb4b4e9c62313a4e8da8c676e142cc210d58e/shellingham-1.5.4.tar.gz", hash = "sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de", size = 10310 } +sdist = { url = "https://files.pythonhosted.org/packages/58/15/8b3609fd3830ef7b27b655beb4b4e9c62313a4e8da8c676e142cc210d58e/shellingham-1.5.4.tar.gz", hash = "sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de", size = 10310, upload-time = "2023-10-24T04:13:40.426Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e0/f9/0595336914c5619e5f28a1fb793285925a8cd4b432c9da0a987836c7f822/shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686", size = 9755 }, + { url = "https://files.pythonhosted.org/packages/e0/f9/0595336914c5619e5f28a1fb793285925a8cd4b432c9da0a987836c7f822/shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686", size = 9755, upload-time = "2023-10-24T04:13:38.866Z" }, ] [[package]] @@ -1912,43 +2497,44 @@ name = "simplejpeg" version = "1.9.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "numpy" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/90/64/da60f0ba80570f9a36c9b6e055f4364bda2c547715296d5773d2ea6d5a60/simplejpeg-1.9.0.tar.gz", hash = "sha256:5ac7d9489eeb812c2e7ea5c283994a29d9fefdfe5ed7b86c09d485e0dd366689", size = 3965764 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/51/1c/787e062aa3ad48b93cbf516f7aff9ade275f2e3cd901e4eb81744959e5bb/simplejpeg-1.9.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:60191ea898d58aaef489a8f94bf34a7472a3ae5a40f16a364f154151f751d08b", size = 425492 }, - { url = "https://files.pythonhosted.org/packages/17/5f/00178980659301d4257499143243fa7b7fa0ad348762072f40b08a0459bc/simplejpeg-1.9.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6cbc0eba5159c9c4b6d2930f429856b4f5b7b792fb48a4c93141e56878c9b71e", size = 401393 }, - { url = "https://files.pythonhosted.org/packages/4d/42/941441677d990e43a53d96c667bf32a3e930855e4807a12e69dedf69c24a/simplejpeg-1.9.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:216ff066e9a05743470ade59ee6014c1a40655bf38a0fc40bae8c78511749a90", size = 448250 }, - { url = "https://files.pythonhosted.org/packages/8e/2f/34c30d9dc903119931f03a1e81112c8f3cd829e833972f6446c0e49ff53f/simplejpeg-1.9.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9cd72c67f1c8fc67f1db432fdae7b03272ca56b72cbb43883c082b63358851c4", size = 405949 }, - { url = "https://files.pythonhosted.org/packages/3a/6a/9952d5c3464f82cf974432ce52a4106ff7b26742eab6e2caa737c28df0ca/simplejpeg-1.9.0-cp311-cp311-win_amd64.whl", hash = "sha256:8f242aa7401b12edfe3b5c76ee4391a30bfba8e0cb93bc5ddb6ff0c2d2bef33c", size = 292682 }, - { url = "https://files.pythonhosted.org/packages/61/94/aed8b242461a3a603331d3c8eb59e4d56de4532b345d68764ad0896cf750/simplejpeg-1.9.0-cp311-cp311-win_arm64.whl", hash = "sha256:0e28186618efc16b02526ad68ecd53ef84babb3c88a7313624ed665dfe4649ac", size = 253544 }, - { url = "https://files.pythonhosted.org/packages/18/05/a932dc6a89cdfd8cdfbd300340d87164eb3daaaf6a1b86b09bf0b87e0c2a/simplejpeg-1.9.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f218b4810f0dcb573bf323dae73177961c235c79588657927d7893a714636ca2", size = 424657 }, - { url = "https://files.pythonhosted.org/packages/44/73/53f7d2e0ce86c9b850301c1c9165dedbac9ac88a6045aa1cb8ad37176c17/simplejpeg-1.9.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f987b5783e0d649457acf136a4544a75f6d40f15cba89b6c5a4583ccf5577957", size = 401461 }, - { url = "https://files.pythonhosted.org/packages/75/c1/0cbf167e3efa32adfbb0674a3504eb118cc5bdc372a44ee937c30324188e/simplejpeg-1.9.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:08ab337ca3b26d7562f5ad686ab8f3966fb206fced607d248e693cbc57fc53b3", size = 448908 }, - { url = "https://files.pythonhosted.org/packages/03/80/44514f83a09500d1eb8ebba8cadd9aa16f7a60690c19dbd98a570ca2c0ec/simplejpeg-1.9.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5be1c8932f43f99b6cc52f8ac4c28e3ac19a1a830351efdb159715fd683e2053", size = 407547 }, - { url = "https://files.pythonhosted.org/packages/6a/d7/115be2e87257c1e148c0f911c020c6442eafb8d164cbd642327d21f22179/simplejpeg-1.9.0-cp312-cp312-win_amd64.whl", hash = "sha256:808b6840f1c6d4de20ae7a086cf9bf49eccac6ef6658df34b4948e071cbe9680", size = 293810 }, - { url = "https://files.pythonhosted.org/packages/49/21/6a4c1589fbcde51a349ef7a629af5867701011bced774389a4f6782ef6cd/simplejpeg-1.9.0-cp312-cp312-win_arm64.whl", hash = "sha256:b65fdde80097cb1fad9c6dad6a12767215c311704f7fad321fbd8501219fad06", size = 253182 }, - { url = "https://files.pythonhosted.org/packages/e3/32/c2d5baa4af82551feae9082d1800c7c7e96586f67292dad4e1442298ad34/simplejpeg-1.9.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:52b4e8e0d68caa3e0962415daff12df2911df36a697e53a75878a45e9e34e9ad", size = 423518 }, - { url = "https://files.pythonhosted.org/packages/84/97/6a4018d4c1c980d9f4c48c29d3d6bfaeb18444dd8e82997246c9950fb79a/simplejpeg-1.9.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:475d1932f50264d63dbc752678b5a6629ed8c6b0f5edfbe4e9cd7881d5f8a1f1", size = 400574 }, - { url = "https://files.pythonhosted.org/packages/88/8b/d8ca384f1362371d61690d7460d3ae4cec4a5a25d9eb06cd15623de3725a/simplejpeg-1.9.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a0c375130f73bb08229a3ded392d84ee2d916b3e87e7ec5d2ac4e47b7144346a", size = 448142 }, - { url = "https://files.pythonhosted.org/packages/cf/0a/58d6d8e997ee01486cfcfd4406a74638f2f63bb65122694b10411dadf1d5/simplejpeg-1.9.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d00feb1cc0348aba0a41db6dbda4db468db92099b1b3d473159e6f68aa990795", size = 406252 }, - { url = "https://files.pythonhosted.org/packages/ae/12/c95aef82037bd2082e9a35b949352e9d8477afec540fefe48c7502114bca/simplejpeg-1.9.0-cp313-cp313-win_amd64.whl", hash = "sha256:7b58f81133040ff7103dee90bb4f949e34456084f86347fb388505f3a0a42895", size = 293831 }, - { url = "https://files.pythonhosted.org/packages/84/cd/41e96d4b82a20d2d448a55a21831c1e57c920f7da485850717da7cf5036a/simplejpeg-1.9.0-cp313-cp313-win_arm64.whl", hash = "sha256:acf6acd6c41a4a42fd9d89cf4d3f3d6a072d0eb5dbc231c1620e165f79a8cad5", size = 253131 }, - { url = "https://files.pythonhosted.org/packages/14/e3/b867cc9b0c82b0252b5ca7c2a94b6cbaa36b7f10dcaa4d6c6db5fc089285/simplejpeg-1.9.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:aa4d0663499aa3d007b3304168735e11556e7a3a60002686455b9c6bf4d31b26", size = 423729 }, - { url = "https://files.pythonhosted.org/packages/66/7a/3f2fd2a638f930bd6a84b956d93de543e29d610fe4a4ad3b8ac558240197/simplejpeg-1.9.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:0605a56f0d9f87d39bc5ac5a8deeae7f080577e56d5e91022f51b7aa27d740d2", size = 401297 }, - { url = "https://files.pythonhosted.org/packages/d4/32/fe632d5709e4a278a73f99539a94fdecf9d48969b8b3b94ba9940d8fcb9d/simplejpeg-1.9.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2192faf8efa84de5965da7336cf4c358c395f06a67ad87b85d513eea52d860c7", size = 450009 }, - { url = "https://files.pythonhosted.org/packages/4d/dc/48db2d81c29ce13f60ab2e5912498f2c6d94afb2f6515bf2a1fc3c1b3046/simplejpeg-1.9.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f22024286577a4e9bb30c4b3c1a66a3b0c6e56801b26c83d0581ad294d1b99e3", size = 407148 }, - { url = "https://files.pythonhosted.org/packages/4f/6d/59d09dd7212618398dad1ab41281bf69d83083f76cef81393e8946bd0ffa/simplejpeg-1.9.0-cp314-cp314-win_amd64.whl", hash = "sha256:6968fe346af7cd32c8ad22f80236308d252e813c374a27d194321cb3b28f56dd", size = 302744 }, - { url = "https://files.pythonhosted.org/packages/70/92/8906322e50d52084877bc08d307c61993881f4ce052d264810548b9aca1f/simplejpeg-1.9.0-cp314-cp314-win_arm64.whl", hash = "sha256:92efd868083bc1cee80a227996cfe56e00c83b5de51ae6c19ce5140c1ba0e089", size = 265715 }, + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, + { name = "numpy", version = "2.5.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/90/64/da60f0ba80570f9a36c9b6e055f4364bda2c547715296d5773d2ea6d5a60/simplejpeg-1.9.0.tar.gz", hash = "sha256:5ac7d9489eeb812c2e7ea5c283994a29d9fefdfe5ed7b86c09d485e0dd366689", size = 3965764, upload-time = "2025-10-10T10:58:08.197Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/51/1c/787e062aa3ad48b93cbf516f7aff9ade275f2e3cd901e4eb81744959e5bb/simplejpeg-1.9.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:60191ea898d58aaef489a8f94bf34a7472a3ae5a40f16a364f154151f751d08b", size = 425492, upload-time = "2025-10-10T10:57:29.067Z" }, + { url = "https://files.pythonhosted.org/packages/17/5f/00178980659301d4257499143243fa7b7fa0ad348762072f40b08a0459bc/simplejpeg-1.9.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6cbc0eba5159c9c4b6d2930f429856b4f5b7b792fb48a4c93141e56878c9b71e", size = 401393, upload-time = "2025-10-10T10:57:30.321Z" }, + { url = "https://files.pythonhosted.org/packages/4d/42/941441677d990e43a53d96c667bf32a3e930855e4807a12e69dedf69c24a/simplejpeg-1.9.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:216ff066e9a05743470ade59ee6014c1a40655bf38a0fc40bae8c78511749a90", size = 448250, upload-time = "2025-10-10T10:57:31.602Z" }, + { url = "https://files.pythonhosted.org/packages/8e/2f/34c30d9dc903119931f03a1e81112c8f3cd829e833972f6446c0e49ff53f/simplejpeg-1.9.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9cd72c67f1c8fc67f1db432fdae7b03272ca56b72cbb43883c082b63358851c4", size = 405949, upload-time = "2025-10-10T10:57:32.837Z" }, + { url = "https://files.pythonhosted.org/packages/3a/6a/9952d5c3464f82cf974432ce52a4106ff7b26742eab6e2caa737c28df0ca/simplejpeg-1.9.0-cp311-cp311-win_amd64.whl", hash = "sha256:8f242aa7401b12edfe3b5c76ee4391a30bfba8e0cb93bc5ddb6ff0c2d2bef33c", size = 292682, upload-time = "2025-10-10T10:57:34.181Z" }, + { url = "https://files.pythonhosted.org/packages/61/94/aed8b242461a3a603331d3c8eb59e4d56de4532b345d68764ad0896cf750/simplejpeg-1.9.0-cp311-cp311-win_arm64.whl", hash = "sha256:0e28186618efc16b02526ad68ecd53ef84babb3c88a7313624ed665dfe4649ac", size = 253544, upload-time = "2025-10-10T10:57:35.412Z" }, + { url = "https://files.pythonhosted.org/packages/18/05/a932dc6a89cdfd8cdfbd300340d87164eb3daaaf6a1b86b09bf0b87e0c2a/simplejpeg-1.9.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f218b4810f0dcb573bf323dae73177961c235c79588657927d7893a714636ca2", size = 424657, upload-time = "2025-10-10T10:57:36.676Z" }, + { url = "https://files.pythonhosted.org/packages/44/73/53f7d2e0ce86c9b850301c1c9165dedbac9ac88a6045aa1cb8ad37176c17/simplejpeg-1.9.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f987b5783e0d649457acf136a4544a75f6d40f15cba89b6c5a4583ccf5577957", size = 401461, upload-time = "2025-10-10T10:57:37.963Z" }, + { url = "https://files.pythonhosted.org/packages/75/c1/0cbf167e3efa32adfbb0674a3504eb118cc5bdc372a44ee937c30324188e/simplejpeg-1.9.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:08ab337ca3b26d7562f5ad686ab8f3966fb206fced607d248e693cbc57fc53b3", size = 448908, upload-time = "2025-10-10T10:57:39.303Z" }, + { url = "https://files.pythonhosted.org/packages/03/80/44514f83a09500d1eb8ebba8cadd9aa16f7a60690c19dbd98a570ca2c0ec/simplejpeg-1.9.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5be1c8932f43f99b6cc52f8ac4c28e3ac19a1a830351efdb159715fd683e2053", size = 407547, upload-time = "2025-10-10T10:57:40.867Z" }, + { url = "https://files.pythonhosted.org/packages/6a/d7/115be2e87257c1e148c0f911c020c6442eafb8d164cbd642327d21f22179/simplejpeg-1.9.0-cp312-cp312-win_amd64.whl", hash = "sha256:808b6840f1c6d4de20ae7a086cf9bf49eccac6ef6658df34b4948e071cbe9680", size = 293810, upload-time = "2025-10-10T10:57:42.498Z" }, + { url = "https://files.pythonhosted.org/packages/49/21/6a4c1589fbcde51a349ef7a629af5867701011bced774389a4f6782ef6cd/simplejpeg-1.9.0-cp312-cp312-win_arm64.whl", hash = "sha256:b65fdde80097cb1fad9c6dad6a12767215c311704f7fad321fbd8501219fad06", size = 253182, upload-time = "2025-10-10T10:57:44.051Z" }, + { url = "https://files.pythonhosted.org/packages/e3/32/c2d5baa4af82551feae9082d1800c7c7e96586f67292dad4e1442298ad34/simplejpeg-1.9.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:52b4e8e0d68caa3e0962415daff12df2911df36a697e53a75878a45e9e34e9ad", size = 423518, upload-time = "2025-10-10T10:57:45.291Z" }, + { url = "https://files.pythonhosted.org/packages/84/97/6a4018d4c1c980d9f4c48c29d3d6bfaeb18444dd8e82997246c9950fb79a/simplejpeg-1.9.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:475d1932f50264d63dbc752678b5a6629ed8c6b0f5edfbe4e9cd7881d5f8a1f1", size = 400574, upload-time = "2025-10-10T10:57:46.475Z" }, + { url = "https://files.pythonhosted.org/packages/88/8b/d8ca384f1362371d61690d7460d3ae4cec4a5a25d9eb06cd15623de3725a/simplejpeg-1.9.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a0c375130f73bb08229a3ded392d84ee2d916b3e87e7ec5d2ac4e47b7144346a", size = 448142, upload-time = "2025-10-10T10:57:47.894Z" }, + { url = "https://files.pythonhosted.org/packages/cf/0a/58d6d8e997ee01486cfcfd4406a74638f2f63bb65122694b10411dadf1d5/simplejpeg-1.9.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d00feb1cc0348aba0a41db6dbda4db468db92099b1b3d473159e6f68aa990795", size = 406252, upload-time = "2025-10-10T10:57:49.158Z" }, + { url = "https://files.pythonhosted.org/packages/ae/12/c95aef82037bd2082e9a35b949352e9d8477afec540fefe48c7502114bca/simplejpeg-1.9.0-cp313-cp313-win_amd64.whl", hash = "sha256:7b58f81133040ff7103dee90bb4f949e34456084f86347fb388505f3a0a42895", size = 293831, upload-time = "2025-10-10T10:57:50.576Z" }, + { url = "https://files.pythonhosted.org/packages/84/cd/41e96d4b82a20d2d448a55a21831c1e57c920f7da485850717da7cf5036a/simplejpeg-1.9.0-cp313-cp313-win_arm64.whl", hash = "sha256:acf6acd6c41a4a42fd9d89cf4d3f3d6a072d0eb5dbc231c1620e165f79a8cad5", size = 253131, upload-time = "2025-10-10T10:57:51.754Z" }, + { url = "https://files.pythonhosted.org/packages/14/e3/b867cc9b0c82b0252b5ca7c2a94b6cbaa36b7f10dcaa4d6c6db5fc089285/simplejpeg-1.9.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:aa4d0663499aa3d007b3304168735e11556e7a3a60002686455b9c6bf4d31b26", size = 423729, upload-time = "2025-10-10T10:57:53.004Z" }, + { url = "https://files.pythonhosted.org/packages/66/7a/3f2fd2a638f930bd6a84b956d93de543e29d610fe4a4ad3b8ac558240197/simplejpeg-1.9.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:0605a56f0d9f87d39bc5ac5a8deeae7f080577e56d5e91022f51b7aa27d740d2", size = 401297, upload-time = "2025-10-10T10:57:54.236Z" }, + { url = "https://files.pythonhosted.org/packages/d4/32/fe632d5709e4a278a73f99539a94fdecf9d48969b8b3b94ba9940d8fcb9d/simplejpeg-1.9.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2192faf8efa84de5965da7336cf4c358c395f06a67ad87b85d513eea52d860c7", size = 450009, upload-time = "2025-10-10T10:57:55.555Z" }, + { url = "https://files.pythonhosted.org/packages/4d/dc/48db2d81c29ce13f60ab2e5912498f2c6d94afb2f6515bf2a1fc3c1b3046/simplejpeg-1.9.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f22024286577a4e9bb30c4b3c1a66a3b0c6e56801b26c83d0581ad294d1b99e3", size = 407148, upload-time = "2025-10-10T10:57:57Z" }, + { url = "https://files.pythonhosted.org/packages/4f/6d/59d09dd7212618398dad1ab41281bf69d83083f76cef81393e8946bd0ffa/simplejpeg-1.9.0-cp314-cp314-win_amd64.whl", hash = "sha256:6968fe346af7cd32c8ad22f80236308d252e813c374a27d194321cb3b28f56dd", size = 302744, upload-time = "2025-10-10T10:57:58.643Z" }, + { url = "https://files.pythonhosted.org/packages/70/92/8906322e50d52084877bc08d307c61993881f4ce052d264810548b9aca1f/simplejpeg-1.9.0-cp314-cp314-win_arm64.whl", hash = "sha256:92efd868083bc1cee80a227996cfe56e00c83b5de51ae6c19ce5140c1ba0e089", size = 265715, upload-time = "2025-10-10T10:57:59.809Z" }, ] [[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 } +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 }, + { 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]] @@ -1958,9 +2544,9 @@ 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 } +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 }, + { 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]] @@ -1978,7 +2564,8 @@ dependencies = [ { name = "keras" }, { name = "libclang" }, { name = "ml-dtypes" }, - { name = "numpy" }, + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, + { name = "numpy", version = "2.5.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, { name = "opt-einsum" }, { name = "packaging" }, { name = "protobuf" }, @@ -1990,27 +2577,27 @@ dependencies = [ { name = "wrapt" }, ] wheels = [ - { url = "https://files.pythonhosted.org/packages/ec/76/b649b02a243c09f0348199dbd81408c1558cfbec2b6d77d820c428a95254/tensorflow-2.21.0-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:51f46f4806139d01fc7920cc72a4c75876f80649bf40ff083ab6212ceb43ca88", size = 223405849 }, - { url = "https://files.pythonhosted.org/packages/9c/7c/57b7ee953b40d5d837136d20af6851b20ea6ca63753fc05699e8bcfab6d5/tensorflow-2.21.0-cp311-cp311-manylinux_2_27_aarch64.whl", hash = "sha256:4b24aca16d527a3cf1c37f720c04c484c3d20f4d0fdfa98632ac8b9f3be6eb5c", size = 281828020 }, - { url = "https://files.pythonhosted.org/packages/26/a1/23b9b39c046c723db63a1c2c93397703efa3400a36f98d9c717b473aafb8/tensorflow-2.21.0-cp311-cp311-manylinux_2_27_x86_64.whl", hash = "sha256:9056fbc9ba04235810b71ae6cbd958a196e8804fb53bbcffbf3e23b56155f124", size = 572440367 }, - { url = "https://files.pythonhosted.org/packages/8f/a2/6d7e6a738e302530586d484895de2cf3fc158ad9c73b4504a670b2956dd9/tensorflow-2.21.0-cp311-cp311-win_amd64.whl", hash = "sha256:0064a19bdc054a4b7c5e7e21cdf50ce38a114c2bada578b4f5267a37410f6784", size = 350834005 }, - { url = "https://files.pythonhosted.org/packages/39/59/27adba20bbd1088b00fc0e9232aa21493b4800af2299eb6005017a6053f4/tensorflow-2.21.0-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:56ecd7d47429acbe1df2694d50b75bf9fc3995ac92cb367cd9af6c4780ead712", size = 223488205 }, - { url = "https://files.pythonhosted.org/packages/20/a5/f139fbbd4e81a2e556170718698acf518a71a84927fa1dc1f5935b6ab3d2/tensorflow-2.21.0-cp312-cp312-manylinux_2_27_aarch64.whl", hash = "sha256:b07d15737406533898e36c7ef7944b1be18e9028dc521b9229952c537bbc5552", size = 281946471 }, - { url = "https://files.pythonhosted.org/packages/ce/d7/6e71b4ded8ce99cd21add95611ca14af6e9ad4f2baeabdeb79a4a6b3cb1f/tensorflow-2.21.0-cp312-cp312-manylinux_2_27_x86_64.whl", hash = "sha256:b3b95643c4e70eb925839938fb35cbe142f317ec84af6844ee61513713bb13c0", size = 572611111 }, - { url = "https://files.pythonhosted.org/packages/7d/0d/4ee4bc074597b41c9c00dc97b4418ef1eb8736fe9186ffdb3961efdfb730/tensorflow-2.21.0-cp312-cp312-win_amd64.whl", hash = "sha256:27ba0682572b1e50a0db1ee74cfb787eafcfdb1b751bbbf401fed62fe32a92e0", size = 350945509 }, - { url = "https://files.pythonhosted.org/packages/40/09/268b45a61be2bce136dabf3a3cd7099c8a984ae398198f71920b4c60c502/tensorflow-2.21.0-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:a145ed46c58192b7c3f9916d070caf4f6afc6dc7e5511f83dd97677f4c4947f4", size = 223766900 }, - { url = "https://files.pythonhosted.org/packages/72/72/343b86b4c9bfe28e81f749439f908c1e26aeac73d9f12b8dcdb996eb8ecb/tensorflow-2.21.0-cp313-cp313-manylinux_2_27_aarch64.whl", hash = "sha256:a10abdfb8b1189210c251021a3f153b7ccc52a8e6521351f3dc3331e8ba593e0", size = 282213003 }, - { url = "https://files.pythonhosted.org/packages/86/6c/10d075ffc09754c7f10e749ba3c9d46dd809fb007990c7f788128044180c/tensorflow-2.21.0-cp313-cp313-manylinux_2_27_x86_64.whl", hash = "sha256:e9d8da8dcab9650efb45f032ba70af2f016f907e6e0c6bda29dd101bba945406", size = 572881074 }, - { url = "https://files.pythonhosted.org/packages/86/91/dedad8403e7b0036d99be4878987693b7b7f62097eb8537fa6ce62ea131c/tensorflow-2.21.0-cp313-cp313-win_amd64.whl", hash = "sha256:76cccbe0a95d9392dee1ae501ae0656b6c73c1cac29a7f8f32d570e0670863f7", size = 351205371 }, + { url = "https://files.pythonhosted.org/packages/ec/76/b649b02a243c09f0348199dbd81408c1558cfbec2b6d77d820c428a95254/tensorflow-2.21.0-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:51f46f4806139d01fc7920cc72a4c75876f80649bf40ff083ab6212ceb43ca88", size = 223405849, upload-time = "2026-03-06T17:23:12.976Z" }, + { url = "https://files.pythonhosted.org/packages/9c/7c/57b7ee953b40d5d837136d20af6851b20ea6ca63753fc05699e8bcfab6d5/tensorflow-2.21.0-cp311-cp311-manylinux_2_27_aarch64.whl", hash = "sha256:4b24aca16d527a3cf1c37f720c04c484c3d20f4d0fdfa98632ac8b9f3be6eb5c", size = 281828020, upload-time = "2026-03-06T17:23:21.243Z" }, + { url = "https://files.pythonhosted.org/packages/26/a1/23b9b39c046c723db63a1c2c93397703efa3400a36f98d9c717b473aafb8/tensorflow-2.21.0-cp311-cp311-manylinux_2_27_x86_64.whl", hash = "sha256:9056fbc9ba04235810b71ae6cbd958a196e8804fb53bbcffbf3e23b56155f124", size = 572440367, upload-time = "2026-03-06T17:23:35.279Z" }, + { url = "https://files.pythonhosted.org/packages/8f/a2/6d7e6a738e302530586d484895de2cf3fc158ad9c73b4504a670b2956dd9/tensorflow-2.21.0-cp311-cp311-win_amd64.whl", hash = "sha256:0064a19bdc054a4b7c5e7e21cdf50ce38a114c2bada578b4f5267a37410f6784", size = 350834005, upload-time = "2026-03-06T17:23:53.021Z" }, + { url = "https://files.pythonhosted.org/packages/39/59/27adba20bbd1088b00fc0e9232aa21493b4800af2299eb6005017a6053f4/tensorflow-2.21.0-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:56ecd7d47429acbe1df2694d50b75bf9fc3995ac92cb367cd9af6c4780ead712", size = 223488205, upload-time = "2026-03-06T17:24:03.48Z" }, + { url = "https://files.pythonhosted.org/packages/20/a5/f139fbbd4e81a2e556170718698acf518a71a84927fa1dc1f5935b6ab3d2/tensorflow-2.21.0-cp312-cp312-manylinux_2_27_aarch64.whl", hash = "sha256:b07d15737406533898e36c7ef7944b1be18e9028dc521b9229952c537bbc5552", size = 281946471, upload-time = "2026-03-06T17:24:11.896Z" }, + { url = "https://files.pythonhosted.org/packages/ce/d7/6e71b4ded8ce99cd21add95611ca14af6e9ad4f2baeabdeb79a4a6b3cb1f/tensorflow-2.21.0-cp312-cp312-manylinux_2_27_x86_64.whl", hash = "sha256:b3b95643c4e70eb925839938fb35cbe142f317ec84af6844ee61513713bb13c0", size = 572611111, upload-time = "2026-03-06T17:24:26.209Z" }, + { url = "https://files.pythonhosted.org/packages/7d/0d/4ee4bc074597b41c9c00dc97b4418ef1eb8736fe9186ffdb3961efdfb730/tensorflow-2.21.0-cp312-cp312-win_amd64.whl", hash = "sha256:27ba0682572b1e50a0db1ee74cfb787eafcfdb1b751bbbf401fed62fe32a92e0", size = 350945509, upload-time = "2026-03-06T17:24:41.65Z" }, + { url = "https://files.pythonhosted.org/packages/40/09/268b45a61be2bce136dabf3a3cd7099c8a984ae398198f71920b4c60c502/tensorflow-2.21.0-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:a145ed46c58192b7c3f9916d070caf4f6afc6dc7e5511f83dd97677f4c4947f4", size = 223766900, upload-time = "2026-03-06T17:24:51.349Z" }, + { url = "https://files.pythonhosted.org/packages/72/72/343b86b4c9bfe28e81f749439f908c1e26aeac73d9f12b8dcdb996eb8ecb/tensorflow-2.21.0-cp313-cp313-manylinux_2_27_aarch64.whl", hash = "sha256:a10abdfb8b1189210c251021a3f153b7ccc52a8e6521351f3dc3331e8ba593e0", size = 282213003, upload-time = "2026-03-06T17:24:59.859Z" }, + { url = "https://files.pythonhosted.org/packages/86/6c/10d075ffc09754c7f10e749ba3c9d46dd809fb007990c7f788128044180c/tensorflow-2.21.0-cp313-cp313-manylinux_2_27_x86_64.whl", hash = "sha256:e9d8da8dcab9650efb45f032ba70af2f016f907e6e0c6bda29dd101bba945406", size = 572881074, upload-time = "2026-03-06T17:25:14.453Z" }, + { url = "https://files.pythonhosted.org/packages/86/91/dedad8403e7b0036d99be4878987693b7b7f62097eb8537fa6ce62ea131c/tensorflow-2.21.0-cp313-cp313-win_amd64.whl", hash = "sha256:76cccbe0a95d9392dee1ae501ae0656b6c73c1cac29a7f8f32d570e0670863f7", size = 351205371, upload-time = "2026-03-06T17:25:33.144Z" }, ] [[package]] name = "termcolor" version = "3.3.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/46/79/cf31d7a93a8fdc6aa0fbb665be84426a8c5a557d9240b6239e9e11e35fc5/termcolor-3.3.0.tar.gz", hash = "sha256:348871ca648ec6a9a983a13ab626c0acce02f515b9e1983332b17af7979521c5", size = 14434 } +sdist = { url = "https://files.pythonhosted.org/packages/46/79/cf31d7a93a8fdc6aa0fbb665be84426a8c5a557d9240b6239e9e11e35fc5/termcolor-3.3.0.tar.gz", hash = "sha256:348871ca648ec6a9a983a13ab626c0acce02f515b9e1983332b17af7979521c5", size = 14434, upload-time = "2025-12-29T12:55:21.882Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/33/d1/8bb87d21e9aeb323cc03034f5eaf2c8f69841e40e4853c2627edf8111ed3/termcolor-3.3.0-py3-none-any.whl", hash = "sha256:cf642efadaf0a8ebbbf4bc7a31cec2f9b5f21a9f726f4ccbb08192c9c26f43a5", size = 7734 }, + { url = "https://files.pythonhosted.org/packages/33/d1/8bb87d21e9aeb323cc03034f5eaf2c8f69841e40e4853c2627edf8111ed3/termcolor-3.3.0-py3-none-any.whl", hash = "sha256:cf642efadaf0a8ebbbf4bc7a31cec2f9b5f21a9f726f4ccbb08192c9c26f43a5", size = 7734, upload-time = "2025-12-29T12:55:20.718Z" }, ] [[package]] @@ -2024,22 +2611,26 @@ resolution-markers = [ "python_full_version < '3.12' and sys_platform == 'darwin'", ] dependencies = [ - { name = "numpy", marker = "python_full_version < '3.12'" }, + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" } }, ] -sdist = { url = "https://files.pythonhosted.org/packages/c5/cb/2f6d79c7576e22c116352a801f4c3c8ace5957e9aced862012430b62e14f/tifffile-2026.3.3.tar.gz", hash = "sha256:d9a1266bed6f2ee1dd0abde2018a38b4f8b2935cb843df381d70ac4eac5458b7", size = 388745 } +sdist = { url = "https://files.pythonhosted.org/packages/c5/cb/2f6d79c7576e22c116352a801f4c3c8ace5957e9aced862012430b62e14f/tifffile-2026.3.3.tar.gz", hash = "sha256:d9a1266bed6f2ee1dd0abde2018a38b4f8b2935cb843df381d70ac4eac5458b7", size = 388745, upload-time = "2026-03-03T19:14:38.134Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/1a/e4/e804505f87627cd8cdae9c010c47c4485fd8c1ce31a7dd0ab7fcc4707377/tifffile-2026.3.3-py3-none-any.whl", hash = "sha256:e8be15c94273113d31ecb7aa3a39822189dd11c4967e3cc88c178f1ad2fd1170", size = 243960 }, + { url = "https://files.pythonhosted.org/packages/1a/e4/e804505f87627cd8cdae9c010c47c4485fd8c1ce31a7dd0ab7fcc4707377/tifffile-2026.3.3-py3-none-any.whl", hash = "sha256:e8be15c94273113d31ecb7aa3a39822189dd11c4967e3cc88c178f1ad2fd1170", size = 243960, upload-time = "2026-03-03T19:14:35.808Z" }, ] [[package]] name = "tifffile" -version = "2026.4.11" +version = "2026.7.14" source = { registry = "https://pypi.org/simple" } resolution-markers = [ - "python_full_version >= '3.14' and sys_platform == 'win32'", - "python_full_version >= '3.14' and sys_platform == 'emscripten'", - "python_full_version >= '3.14' and sys_platform != 'darwin' and sys_platform != 'emscripten' and sys_platform != 'win32'", - "python_full_version >= '3.14' and sys_platform == 'darwin'", + "python_full_version >= '3.15' and sys_platform == 'win32'", + "python_full_version == '3.14.*' and sys_platform == 'win32'", + "python_full_version >= '3.15' and sys_platform == 'emscripten'", + "python_full_version == '3.14.*' and sys_platform == 'emscripten'", + "python_full_version >= '3.15' and sys_platform != 'darwin' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.14.*' and sys_platform != 'darwin' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version >= '3.15' and sys_platform == 'darwin'", + "python_full_version == '3.14.*' and sys_platform == 'darwin'", "python_full_version == '3.13.*' and sys_platform == 'win32'", "python_full_version == '3.12.*' and sys_platform == 'win32'", "python_full_version == '3.13.*' and sys_platform == 'emscripten'", @@ -2050,220 +2641,237 @@ resolution-markers = [ "python_full_version == '3.12.*' and sys_platform == 'darwin'", ] dependencies = [ - { name = "numpy", marker = "python_full_version >= '3.12'" }, + { name = "numpy", version = "2.5.1", source = { registry = "https://pypi.org/simple" } }, ] -sdist = { url = "https://files.pythonhosted.org/packages/d7/4a/e687f5957fead200faad58dbf9c9431a2bbb118040e96f5fb8a55f7ebc50/tifffile-2026.4.11.tar.gz", hash = "sha256:17758ff0c0d4db385792a083ad3ca51fcb0f4d942642f4d8f8bc1287fdcf17bc", size = 394956 } +sdist = { url = "https://files.pythonhosted.org/packages/20/2f/e5fe51c8f782241d86fdf7251594b195f0d6c2fcf9d389079de212599246/tifffile-2026.7.14.tar.gz", hash = "sha256:ce2703e5ef22c868f1528d5f5b4ef75eefb019cf628a1c9ec0d17e0afeca8ef5", size = 437660, upload-time = "2026-07-14T23:41:31.737Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/3f/9f/74f110b4271ded519c7add4341cbabc824de26817ff1c345b3109df9e99c/tifffile-2026.4.11-py3-none-any.whl", hash = "sha256:9b94ffeddb39e97601af646345e8808f885773de01b299e480ed6d3a41509ec9", size = 248227 }, + { url = "https://files.pythonhosted.org/packages/c8/e8/d381de4a3cc4e3682cba0338f43250893508aad0b310af1d0635f7b04413/tifffile-2026.7.14-py3-none-any.whl", hash = "sha256:4eb20372e76edf2c9fed922b1e3a0a0567be3560bd2008336115763bb1f3c034", size = 270614, upload-time = "2026-07-14T23:41:30.078Z" }, ] [[package]] name = "torch" -version = "2.11.0" +version = "2.13.0" source = { registry = "https://download.pytorch.org/whl/cpu" } resolution-markers = [ - "python_full_version >= '3.14' and sys_platform == 'darwin'", + "python_full_version >= '3.15' and sys_platform == 'darwin'", + "python_full_version == '3.14.*' and sys_platform == 'darwin'", "python_full_version == '3.13.*' and sys_platform == 'darwin'", "python_full_version == '3.12.*' and sys_platform == 'darwin'", "python_full_version < '3.12' and sys_platform == 'darwin'", ] dependencies = [ - { name = "filelock", marker = "sys_platform == 'darwin'" }, - { name = "fsspec", marker = "sys_platform == 'darwin'" }, - { name = "jinja2", marker = "sys_platform == 'darwin'" }, - { name = "networkx", marker = "sys_platform == 'darwin'" }, - { name = "setuptools", marker = "sys_platform == 'darwin'" }, - { name = "sympy", marker = "sys_platform == 'darwin'" }, - { name = "typing-extensions", marker = "sys_platform == 'darwin'" }, + { name = "filelock" }, + { name = "fsspec" }, + { name = "jinja2" }, + { name = "networkx" }, + { name = "setuptools" }, + { name = "sympy" }, + { name = "typing-extensions" }, ] wheels = [ - { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.11.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d75eadcd97fe0dc7cd0eedc4d72152484c19cb2cfe46ce55766c8e129116425f" }, - { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.11.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:43b35116802c85fb88d99f4a396b8bd4472bfca1dd82e69499e5a4f9b8b4e252" }, - { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.11.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:442ec9dc78592564fdad69cf0beaa9da2f82ab810ccb4f13903869a90bf3f15d" }, - { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.11.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:cc3a195701bba2239c313ee311487f80f8aaebe9e89b9073dddbcf2f93b5a0ba" }, - { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.11.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:072a0d6e4865e8b0dc0dbfe6ebed68fae235124222835ef03e5814d414d8c012" }, - { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.11.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:23ec7789017da9d95b6d543d790814785e6f30905c5443efa8257d1490d73f79" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.13.0-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:e76f9bcecc52b8ff711239a2f7547d5353df95878ab232f0773c1d95928b92f8", upload-time = "2026-07-08T12:26:13Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.13.0-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:2fe228aba290d14b9f31b049be550dbd469c3fd3013d7a19705b30454da97027", upload-time = "2026-07-08T12:26:18Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.13.0-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:33449899ce5496c1b84b4853179d94fd102028ae1407314d9fb956bb79e70d09", upload-time = "2026-07-08T12:26:23Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.13.0-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:d849b390e07d8d333ce8ecaf91b273c656c598379a19c9acf1318a883f6b391c", upload-time = "2026-07-08T12:26:28Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.13.0-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:c28def70706c2f9ecc752574766e8ae4da9b810ab6676b611166761a78a9f1e1", upload-time = "2026-07-08T12:26:33Z" }, ] [[package]] name = "torch" -version = "2.11.0+cpu" +version = "2.13.0+cpu" source = { registry = "https://download.pytorch.org/whl/cpu" } resolution-markers = [ - "python_full_version >= '3.14' and sys_platform == 'win32'", - "python_full_version >= '3.14' and sys_platform == 'emscripten'", - "python_full_version >= '3.14' and sys_platform != 'darwin' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version >= '3.15' and sys_platform == 'win32'", + "python_full_version == '3.14.*' and sys_platform == 'win32'", + "python_full_version >= '3.15' and sys_platform == 'emscripten'", + "python_full_version == '3.14.*' and sys_platform == 'emscripten'", + "python_full_version >= '3.15' and sys_platform != 'darwin' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.14.*' and sys_platform != 'darwin' and sys_platform != 'emscripten' 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.13.*' and sys_platform == 'emscripten'", "python_full_version == '3.12.*' and sys_platform == 'emscripten'", - "python_full_version < '3.12' and sys_platform == 'emscripten'", "python_full_version == '3.13.*' and sys_platform != 'darwin' and sys_platform != 'emscripten' and sys_platform != 'win32'", "python_full_version == '3.12.*' and sys_platform != 'darwin' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version < '3.12' and sys_platform == 'win32'", + "python_full_version < '3.12' and sys_platform == 'emscripten'", "python_full_version < '3.12' and sys_platform != 'darwin' and sys_platform != 'emscripten' and sys_platform != 'win32'", ] dependencies = [ - { name = "filelock", marker = "sys_platform != 'darwin'" }, - { name = "fsspec", marker = "sys_platform != 'darwin'" }, - { name = "jinja2", marker = "sys_platform != 'darwin'" }, - { name = "networkx", marker = "sys_platform != 'darwin'" }, - { name = "setuptools", marker = "sys_platform != 'darwin'" }, - { name = "sympy", marker = "sys_platform != 'darwin'" }, - { name = "typing-extensions", marker = "sys_platform != 'darwin'" }, -] -wheels = [ - { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.11.0%2Bcpu-cp311-cp311-linux_s390x.whl" }, - { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.11.0%2Bcpu-cp311-cp311-manylinux_2_28_aarch64.whl" }, - { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.11.0%2Bcpu-cp311-cp311-manylinux_2_28_x86_64.whl" }, - { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.11.0%2Bcpu-cp311-cp311-win_amd64.whl" }, - { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.11.0%2Bcpu-cp312-cp312-linux_s390x.whl" }, - { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.11.0%2Bcpu-cp312-cp312-manylinux_2_28_aarch64.whl" }, - { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.11.0%2Bcpu-cp312-cp312-manylinux_2_28_x86_64.whl" }, - { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.11.0%2Bcpu-cp312-cp312-win_amd64.whl" }, - { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.11.0%2Bcpu-cp313-cp313-linux_s390x.whl" }, - { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.11.0%2Bcpu-cp313-cp313-manylinux_2_28_aarch64.whl" }, - { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.11.0%2Bcpu-cp313-cp313-manylinux_2_28_x86_64.whl" }, - { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.11.0%2Bcpu-cp313-cp313-win_amd64.whl" }, - { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.11.0%2Bcpu-cp313-cp313t-linux_s390x.whl" }, - { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.11.0%2Bcpu-cp313-cp313t-manylinux_2_28_aarch64.whl" }, - { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.11.0%2Bcpu-cp313-cp313t-manylinux_2_28_x86_64.whl" }, - { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.11.0%2Bcpu-cp313-cp313t-win_amd64.whl" }, - { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.11.0%2Bcpu-cp314-cp314-linux_s390x.whl" }, - { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.11.0%2Bcpu-cp314-cp314-manylinux_2_28_aarch64.whl" }, - { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.11.0%2Bcpu-cp314-cp314-manylinux_2_28_x86_64.whl" }, - { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.11.0%2Bcpu-cp314-cp314-win_amd64.whl" }, - { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.11.0%2Bcpu-cp314-cp314t-linux_s390x.whl" }, - { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.11.0%2Bcpu-cp314-cp314t-manylinux_2_28_aarch64.whl" }, - { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.11.0%2Bcpu-cp314-cp314t-manylinux_2_28_x86_64.whl" }, - { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.11.0%2Bcpu-cp314-cp314t-win_amd64.whl" }, + { name = "filelock" }, + { name = "fsspec" }, + { name = "jinja2" }, + { name = "networkx" }, + { name = "setuptools" }, + { name = "sympy" }, + { name = "typing-extensions" }, +] +wheels = [ + { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.13.0%2Bcpu-cp311-cp311-linux_s390x.whl", hash = "sha256:6e9817dbdf5ea76789babd46e457eac5bf14ff566cf85f8addbfdff2d56601ce", upload-time = "2026-07-08T19:27:52Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.13.0%2Bcpu-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:84453b69508ec79902f899c5ed9495acb9e2bbe9fda5f1d5d6f19e3c3842e1a7", upload-time = "2026-07-08T19:28:03Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.13.0%2Bcpu-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:6746dbcbeb526eb61330b76b41ff1b4eb848951103a892eeb080dfa2b264667b", upload-time = "2026-07-08T19:28:16Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.13.0%2Bcpu-cp311-cp311-win_amd64.whl", hash = "sha256:10717d8b3b67c45a4788bf7ffc0bab1ea1e5ebbedd24466be6100102d141fac1", upload-time = "2026-07-08T19:28:27Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.13.0%2Bcpu-cp311-cp311-win_arm64.whl", hash = "sha256:2b3d093abd919ad934c43d47e73ba63ceba7cbd7269fc2e9c1e4fc29e8fe45fa", upload-time = "2026-07-08T19:28:33Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.13.0%2Bcpu-cp312-cp312-linux_s390x.whl", hash = "sha256:ffadde149901c8afa138daa38d898264003cfcf1a3336ca5cd964b5af227d867", upload-time = "2026-07-08T19:28:41Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.13.0%2Bcpu-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:6f307c2c32d764ffc6ff6893b801fad6d4752f3e67966cb8abf1843427c02604", upload-time = "2026-07-08T19:28:51Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.13.0%2Bcpu-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:4ca4a9394b0c771238a4f73590fdbbc4debad85ed0fa63d026ae1b085da7d6e2", upload-time = "2026-07-08T19:29:03Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.13.0%2Bcpu-cp312-cp312-win_amd64.whl", hash = "sha256:a8b450c1e58e5800e5b4691dac412f8d2d65a1dc3298166f91596603a3531e6f", upload-time = "2026-07-08T19:29:15Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.13.0%2Bcpu-cp312-cp312-win_arm64.whl", hash = "sha256:fa0762705b933624d59f6823db9ce7ec2e35b3e1e9c319c9db51fbeecfc3e319", upload-time = "2026-07-08T19:29:21Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.13.0%2Bcpu-cp313-cp313-linux_s390x.whl", hash = "sha256:966d020354f465672dc7dd10d3a5c6cd17d7eb48620aa1d265b48a1f78f06898", upload-time = "2026-07-08T19:29:30Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.13.0%2Bcpu-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:0b8f7d0423027ae8b90c7977c627f3379f325363a08224dffad9b4b2d684a83d", upload-time = "2026-07-08T19:29:40Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.13.0%2Bcpu-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:3fbf9c9d1f3c10c2d59d04aca426dee9ccc6ceb32d255c61e93acc3b4f75fae6", upload-time = "2026-07-08T19:29:54Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.13.0%2Bcpu-cp313-cp313-win_amd64.whl", hash = "sha256:a17ff48608634db245e17e8bb00a9558554a49aeb1e4f5fe6cd039af2a10515b", upload-time = "2026-07-08T19:30:05Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.13.0%2Bcpu-cp313-cp313-win_arm64.whl", hash = "sha256:ac7aaf322be4777765a53bed7264a214dd81b3a1d276b93150515a3c5f75e4b0", upload-time = "2026-07-08T19:30:12Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.13.0%2Bcpu-cp314-cp314-linux_s390x.whl", hash = "sha256:dec241fef3984c0d1edadd1f58708e218d4eae881ceef7bc10cf9964d41b68b9", upload-time = "2026-07-08T19:30:20Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.13.0%2Bcpu-cp314-cp314-manylinux_2_28_aarch64.whl", hash = "sha256:ca021f9eb2f8345c83fa03e3a04587308afb8df71bd472670b3ece00df58621c", upload-time = "2026-07-08T19:30:32Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.13.0%2Bcpu-cp314-cp314-manylinux_2_28_x86_64.whl", hash = "sha256:d20fa53ee744502fa4c69818a720b05ca0d37abd055d4f6e66cae155114bc691", upload-time = "2026-07-08T19:30:45Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.13.0%2Bcpu-cp314-cp314-win_amd64.whl", hash = "sha256:e2e5134decf00e218da62318f3dc5df156231d367871918e91eba95ab0ad43ab", upload-time = "2026-07-08T19:30:58Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.13.0%2Bcpu-cp314-cp314t-linux_s390x.whl", hash = "sha256:991cc14b39e751122c01f017be6448533989868731cb5eecd1006893d26787c2", upload-time = "2026-07-08T19:31:09Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.13.0%2Bcpu-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:7b8d26e29bceafbdaa8d63bfe7612f23875b5af2cc07e13f809c3ed890bbe1d8", upload-time = "2026-07-08T19:31:21Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.13.0%2Bcpu-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:b222c15a0fc2ce207d1c1a59700b46c8fa6748df1f447ad11e5c870dde0933d9", upload-time = "2026-07-08T19:31:35Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.13.0%2Bcpu-cp314-cp314t-win_amd64.whl", hash = "sha256:a43376bd094124ef626bfdd3d4c2c62eacb0b5ddc99776f4a32d4fd16f1f3420", upload-time = "2026-07-08T19:31:48Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.13.0%2Bcpu-cp315-cp315-manylinux_2_28_aarch64.whl", hash = "sha256:8eb5002ca81af00ae69b57540f615b58b8ae922b6d4848176b366a52bd2196e6", upload-time = "2026-07-08T19:32:00Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.13.0%2Bcpu-cp315-cp315-manylinux_2_28_x86_64.whl", hash = "sha256:1a3a35229fdc13446b4eab50e7fcf9399ff941e89a3b761497786297a5d8dde5", upload-time = "2026-07-08T19:32:16Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.13.0%2Bcpu-cp315-cp315t-manylinux_2_28_aarch64.whl", hash = "sha256:8e109528e6bab044815daebaf71770fbaace3a66ef1c816cb55c875350f78a60", upload-time = "2026-07-08T19:32:30Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.13.0%2Bcpu-cp315-cp315t-manylinux_2_28_x86_64.whl", hash = "sha256:222a6681467cc7f6f05cd3068dfbc603def3a1e46d1d4620c1c8cdf6178bd563", upload-time = "2026-07-08T19:32:44Z" }, ] [[package]] name = "torchvision" -version = "0.26.0" +version = "0.28.0" source = { registry = "https://download.pytorch.org/whl/cpu" } resolution-markers = [ - "python_full_version >= '3.14' and sys_platform == 'darwin'", + "python_full_version >= '3.15' and sys_platform == 'darwin'", + "python_full_version == '3.14.*' and sys_platform == 'darwin'", "python_full_version == '3.13.*' and sys_platform == 'darwin'", "python_full_version == '3.12.*' and sys_platform == 'darwin'", "python_full_version < '3.12' and sys_platform == 'darwin'", ] dependencies = [ - { name = "numpy", marker = "sys_platform == 'darwin'" }, - { name = "pillow", marker = "sys_platform == 'darwin'" }, - { name = "torch", version = "2.11.0", source = { registry = "https://download.pytorch.org/whl/cpu" }, marker = "sys_platform == 'darwin'" }, + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, + { name = "numpy", version = "2.5.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, + { name = "pillow" }, + { name = "torch", version = "2.13.0", source = { registry = "https://download.pytorch.org/whl/cpu" } }, ] wheels = [ - { url = "https://download-r2.pytorch.org/whl/cpu/torchvision-0.26.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:55bd6ad4ae77be01ba67a410b05b51f53b0d0ee45f146eb6a0dfb9007e70ab3c" }, - { url = "https://download-r2.pytorch.org/whl/cpu/torchvision-0.26.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c409e1c3fdebec7a3834465086dbda8bf7680eff79abf7fd2f10c6b59520a7a4" }, - { url = "https://download-r2.pytorch.org/whl/cpu/torchvision-0.26.0-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:5d63dd43162691258b1b3529b9041bac7d54caa37eae0925f997108268cbf7c4" }, - { url = "https://download-r2.pytorch.org/whl/cpu/torchvision-0.26.0-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:358fc4726d0c08615b6d83b3149854f11efb2a564ed1acb6fce882e151412d23" }, - { url = "https://download-r2.pytorch.org/whl/cpu/torchvision-0.26.0-cp314-cp314-macosx_12_0_arm64.whl", hash = "sha256:eb61804eb9dbe88c5a2a6c4da8dec1d80d2d0a6f18c999c524e32266cb1ebcd3" }, - { url = "https://download-r2.pytorch.org/whl/cpu/torchvision-0.26.0-cp314-cp314t-macosx_12_0_arm64.whl", hash = "sha256:b7d3e295624a28b3b1769228ce1345d94cf4d390dd31136766f76f2d20f718da" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torchvision-0.28.0-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:83fe6c020866a85acd7d97deccc45ff11d66daf42916d04396a4309c66c0ccb8", upload-time = "2026-07-08T12:26:40Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torchvision-0.28.0-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:e9f54c30cd52e3ef7fd034cc69b7bb7e0964e1c8f8743e018ab92e95b40f9eee", upload-time = "2026-07-08T12:26:40Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torchvision-0.28.0-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:d483b4aa3f5237569053f749cd1a2b5bb548ca456e40461a5dd087f21149d123", upload-time = "2026-07-08T12:26:40Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torchvision-0.28.0-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:3bd9dba55224a9db4a2d77f6feaa5651770d8c8e86d3d0ddb0fa6bec54c8712b", upload-time = "2026-07-08T12:26:40Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torchvision-0.28.0-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:09ce8f56e81f19b9c378ae7bb109f83f6659fd8bc3cd14241a48e4af46e9ed49", upload-time = "2026-07-08T12:26:40Z" }, ] [[package]] name = "torchvision" -version = "0.26.0+cpu" +version = "0.28.0+cpu" source = { registry = "https://download.pytorch.org/whl/cpu" } resolution-markers = [ - "python_full_version >= '3.14' and sys_platform == 'win32'", - "python_full_version >= '3.14' and sys_platform == 'emscripten'", - "python_full_version >= '3.14' and sys_platform != 'darwin' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version >= '3.15' and sys_platform == 'win32'", + "python_full_version == '3.14.*' and sys_platform == 'win32'", + "python_full_version >= '3.15' and sys_platform == 'emscripten'", + "python_full_version == '3.14.*' and sys_platform == 'emscripten'", + "python_full_version >= '3.15' and sys_platform != 'darwin' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.14.*' and sys_platform != 'darwin' and sys_platform != 'emscripten' 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.13.*' and sys_platform == 'emscripten'", "python_full_version == '3.12.*' and sys_platform == 'emscripten'", - "python_full_version < '3.12' and sys_platform == 'emscripten'", "python_full_version == '3.13.*' and sys_platform != 'darwin' and sys_platform != 'emscripten' and sys_platform != 'win32'", "python_full_version == '3.12.*' and sys_platform != 'darwin' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version < '3.12' and sys_platform == 'win32'", + "python_full_version < '3.12' and sys_platform == 'emscripten'", "python_full_version < '3.12' and sys_platform != 'darwin' and sys_platform != 'emscripten' and sys_platform != 'win32'", ] dependencies = [ - { name = "numpy", marker = "sys_platform != 'darwin'" }, - { name = "pillow", marker = "sys_platform != 'darwin'" }, - { name = "torch", version = "2.11.0+cpu", source = { registry = "https://download.pytorch.org/whl/cpu" }, marker = "sys_platform != 'darwin'" }, -] -wheels = [ - { url = "https://download-r2.pytorch.org/whl/cpu/torchvision-0.26.0%2Bcpu-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:c11e55041f6b84a6c4fb28981b901475aa81c38695ccec6ddfcc54c3fa9fac4f" }, - { url = "https://download-r2.pytorch.org/whl/cpu/torchvision-0.26.0%2Bcpu-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:7fcc4584e9f2f8914f898a01437f25b16222fb0bfb3fdeba59cb1b0c640b0995" }, - { url = "https://download-r2.pytorch.org/whl/cpu/torchvision-0.26.0%2Bcpu-cp311-cp311-win_amd64.whl", hash = "sha256:f0c80af8e2807d52f3d480d9828d550f097ad55589f28aab4d65471b3d636359" }, - { url = "https://download-r2.pytorch.org/whl/cpu/torchvision-0.26.0%2Bcpu-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:17f0b542331fc94230b4214c6d123f038af7330fd81019608c0d2402f3bc3079" }, - { url = "https://download-r2.pytorch.org/whl/cpu/torchvision-0.26.0%2Bcpu-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:cf547dc0975eb40bc3249be4ccbeb736597d2c3ece305b1c4e5b7a5dd7363567" }, - { url = "https://download-r2.pytorch.org/whl/cpu/torchvision-0.26.0%2Bcpu-cp312-cp312-win_amd64.whl", hash = "sha256:52aa8401850a9792e71a8a1e65ac004e2b23622a6b6fd278cd11179efbefc65b" }, - { url = "https://download-r2.pytorch.org/whl/cpu/torchvision-0.26.0%2Bcpu-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:2e932af123a39137815dfd152c64cc683fa7cbd327c965e807c9728c7aa4971a" }, - { url = "https://download-r2.pytorch.org/whl/cpu/torchvision-0.26.0%2Bcpu-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:16c4f11eda096dc377e82238c8ebb26c7013622c0f1b2c4dcf85fc70f96c0ea7" }, - { url = "https://download-r2.pytorch.org/whl/cpu/torchvision-0.26.0%2Bcpu-cp313-cp313-win_amd64.whl", hash = "sha256:34ac55a1f614baca2e0f5cef20ddb36184ee3503423871260e1ddd72caf9cb5f" }, - { url = "https://download-r2.pytorch.org/whl/cpu/torchvision-0.26.0%2Bcpu-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:3d30ce3444698807d4b18b199645cd7a95e0b16a4cd0909b8aab47c562a7673a" }, - { url = "https://download-r2.pytorch.org/whl/cpu/torchvision-0.26.0%2Bcpu-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:870a97101168d4da68039d3d51f0c781047065e82dc4c19b2eb0ddff08486180" }, - { url = "https://download-r2.pytorch.org/whl/cpu/torchvision-0.26.0%2Bcpu-cp313-cp313t-win_amd64.whl", hash = "sha256:050aaf28cff9c2981ec72dc3f9b4ef77bcf9c9c99330ce426cb06c5bb9e6e726" }, - { url = "https://download-r2.pytorch.org/whl/cpu/torchvision-0.26.0%2Bcpu-cp314-cp314-manylinux_2_28_aarch64.whl", hash = "sha256:78576c8d5a8665de6caaa6e7c3a3fb7caa5dc112032ba60e129a9e78a446a03b" }, - { url = "https://download-r2.pytorch.org/whl/cpu/torchvision-0.26.0%2Bcpu-cp314-cp314-manylinux_2_28_x86_64.whl", hash = "sha256:78e88d0a57bfadcd17042aa92fe4dd1059e48fcaa2e54a10ac7f438c2eca10d5" }, - { url = "https://download-r2.pytorch.org/whl/cpu/torchvision-0.26.0%2Bcpu-cp314-cp314-win_amd64.whl", hash = "sha256:93144d0997c51b27996c8305df4d9104efb0d38c9a9b6b05c8bc20ebdf7193b5" }, - { url = "https://download-r2.pytorch.org/whl/cpu/torchvision-0.26.0%2Bcpu-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:93a11b159613ad920b1d42c4eb4e585f48e5dff895f3e08f517ef482fe84e130" }, - { url = "https://download-r2.pytorch.org/whl/cpu/torchvision-0.26.0%2Bcpu-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:99f86ec0a83b9e4b5428a452bf667f99a9ae27d4c32bd4b2081fe917303e7710" }, - { url = "https://download-r2.pytorch.org/whl/cpu/torchvision-0.26.0%2Bcpu-cp314-cp314t-win_amd64.whl", hash = "sha256:6139108231a29ffb607931360ee24594553a939467c65530f734a2ed9918f011" }, + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, + { name = "numpy", version = "2.5.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, + { name = "pillow" }, + { name = "torch", version = "2.13.0+cpu", source = { registry = "https://download.pytorch.org/whl/cpu" } }, +] +wheels = [ + { url = "https://download-r2.pytorch.org/whl/cpu/torchvision-0.28.0%2Bcpu-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:22958193d72444ed7cbcc665ba4821a31e5279f9c4d1ad08520918b30896b78a", upload-time = "2026-07-08T12:26:39Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torchvision-0.28.0%2Bcpu-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:1dad604dfc0177ecebe0891bd9701fe2c62ec3f7819a247be541b3fb6effee99", upload-time = "2026-07-08T12:26:39Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torchvision-0.28.0%2Bcpu-cp311-cp311-win_amd64.whl", hash = "sha256:7b6667fd0172463be2a271fb0dbd44b31a7891afd549a66208613ce4cdd79f88", upload-time = "2026-07-08T12:26:39Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torchvision-0.28.0%2Bcpu-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:2f768c4f6d5adf6d5535061fd69ec44827608bac0e96e12114942a6fdfce1107", upload-time = "2026-07-08T12:26:39Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torchvision-0.28.0%2Bcpu-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:b545d46f4d2f9d30381281cf22874bfe1d32a8a7b0ee8396fccde89f30c6a9d9", upload-time = "2026-07-08T12:26:40Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torchvision-0.28.0%2Bcpu-cp312-cp312-win_amd64.whl", hash = "sha256:d88db83abbdfb97199979ec94dd427bc372c1b9ab01f0dbed20af05b0bd644b1", upload-time = "2026-07-08T12:26:40Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torchvision-0.28.0%2Bcpu-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:879ae6d4e2e3651582fb7187eafd535601cb5d019595d47e2c874262a000e88e", upload-time = "2026-07-08T12:26:39Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torchvision-0.28.0%2Bcpu-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:c6373ec4c2f922e89f45ac91889404d312ba29a31f205b0ad9a725a3894ca246", upload-time = "2026-07-08T12:26:39Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torchvision-0.28.0%2Bcpu-cp313-cp313-win_amd64.whl", hash = "sha256:d63eae114b4d1fca2b294d300cea3f0d6c71b6d132641e0c4cab1aa06a467b0d", upload-time = "2026-07-08T12:26:39Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torchvision-0.28.0%2Bcpu-cp314-cp314-manylinux_2_28_aarch64.whl", hash = "sha256:3a1a76c8decb1d7bbedd3588bccc90fb269944b7321a773db181735b42115422", upload-time = "2026-07-08T12:26:39Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torchvision-0.28.0%2Bcpu-cp314-cp314-manylinux_2_28_x86_64.whl", hash = "sha256:1aa741ae0eb8668b6287dd667548e2dd10179c828db68bfdee1519763b9c5b99", upload-time = "2026-07-08T12:26:39Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torchvision-0.28.0%2Bcpu-cp314-cp314-win_amd64.whl", hash = "sha256:d2a0171faa211b506c4dcf3a036942a41077c5d2d3d94883dafbda7b8624a3eb", upload-time = "2026-07-08T12:26:39Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torchvision-0.28.0%2Bcpu-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:8d8b98608779c770ede5e20609772453ebc7487ebb8697445d1856466c542f45", upload-time = "2026-07-08T12:26:39Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torchvision-0.28.0%2Bcpu-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:870d0d42f2eb80f4870cd35e51eea52f596a408a671b28136f06a808846f24c5", upload-time = "2026-07-08T12:26:39Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torchvision-0.28.0%2Bcpu-cp314-cp314t-win_amd64.whl", hash = "sha256:82dbffb63d61cd43d9c7a311588e665aa2b21173a05f852b4d384d6782fd88ef", upload-time = "2026-07-08T12:26:39Z" }, ] [[package]] name = "tqdm" -version = "4.67.3" +version = "4.70.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama", marker = "sys_platform == 'win32'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/09/a9/6ba95a270c6f1fbcd8dac228323f2777d886cb206987444e4bce66338dd4/tqdm-4.67.3.tar.gz", hash = "sha256:7d825f03f89244ef73f1d4ce193cb1774a8179fd96f31d7e1dcde62092b960bb", size = 169598 } +sdist = { url = "https://files.pythonhosted.org/packages/21/3b/6c24bec5be5e743ffd99576daa5cc077722fc7d5bbc00bd133fa0c698dc6/tqdm-4.70.0.tar.gz", hash = "sha256:55b0b0dbd97462d06ebee91e4dac24ed4d4702be82b24f07e6c1d27e08cea220", size = 795438, upload-time = "2026-07-27T11:33:15.271Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/16/e1/3079a9ff9b8e11b846c6ac5c8b5bfb7ff225eee721825310c91b3b50304f/tqdm-4.67.3-py3-none-any.whl", hash = "sha256:ee1e4c0e59148062281c49d80b25b67771a127c85fc9676d3be5f243206826bf", size = 78374 }, + { url = "https://files.pythonhosted.org/packages/f9/1c/01bfd571a64e7f270e6bab5e33777debe0edc56759233ce84f27dec92d14/tqdm-4.70.0-py3-none-any.whl", hash = "sha256:7f585706bfddbdebf89daac705b2dfcc16890130727d3197ca62c732b4310953", size = 80184, upload-time = "2026-07-27T11:33:13.167Z" }, ] [[package]] name = "typer" -version = "0.24.1" +version = "0.27.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "annotated-doc" }, - { name = "click" }, + { name = "colorama", marker = "sys_platform == 'win32'" }, { name = "rich" }, { name = "shellingham" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/f5/24/cb09efec5cc954f7f9b930bf8279447d24618bb6758d4f6adf2574c41780/typer-0.24.1.tar.gz", hash = "sha256:e39b4732d65fbdcde189ae76cf7cd48aeae72919dea1fdfc16593be016256b45", size = 118613 } +sdist = { url = "https://files.pythonhosted.org/packages/37/78/fda3361b56efc27944f24225f6ecd13d96d6fcfe37bd0eb34e2f4c63f9fc/typer-0.27.0.tar.gz", hash = "sha256:629bd12ea5d13a17148125d9a264f949eb171fb3f120f9b04d85873cab054fa5", size = 203430, upload-time = "2026-07-15T19:21:07.007Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/40/03/26a383c9e58c213199d1aad1c3d353cfc22d4444ec6d2c0bf8ad02523843/typer-0.27.0-py3-none-any.whl", hash = "sha256:6f4b27631e47f077871b7dc30e933ec0131c1390fbe0e387ea5574b5bac9ccf1", size = 122716, upload-time = "2026-07-15T19:21:05.553Z" }, +] + +[[package]] +name = "types-pyyaml" +version = "6.0.12.20260724" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/3f/6f/a28f44bcd56bebed42b028a2894c79853e2f5e6b5279e633cb3f287a05e7/types_pyyaml-6.0.12.20260724.tar.gz", hash = "sha256:3c1ce1bb73cd5ec02e90390c2b1f00e810d241d8825fd73ff359696839271b6b", size = 17893, upload-time = "2026-07-24T04:58:43.453Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/4a/91/48db081e7a63bb37284f9fbcefda7c44c277b18b0e13fbc36ea2335b71e6/typer-0.24.1-py3-none-any.whl", hash = "sha256:112c1f0ce578bfb4cab9ffdabc68f031416ebcc216536611ba21f04e9aa84c9e", size = 56085 }, + { url = "https://files.pythonhosted.org/packages/8b/42/0337fefc615e20ee55d1c8f71b774a9b2b734a04669139c20753b27a2a3a/types_pyyaml-6.0.12.20260724-py3-none-any.whl", hash = "sha256:d57db930a4b2efbc57cf430ec8882765d246929432fa253092f383902329a453", size = 20312, upload-time = "2026-07-24T04:58:42.486Z" }, ] [[package]] name = "typing-extensions" -version = "4.15.0" +version = "4.16.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391 } +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/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614 }, + { 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 = "tzdata" -version = "2026.1" +version = "2026.3" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/19/f5/cd531b2d15a671a40c0f66cf06bc3570a12cd56eef98960068ebbad1bf5a/tzdata-2026.1.tar.gz", hash = "sha256:67658a1903c75917309e753fdc349ac0efd8c27db7a0cb406a25be4840f87f98", size = 197639 } +sdist = { url = "https://files.pythonhosted.org/packages/92/ff/5a28bdfd8c3ebec42564ac7d0e54ca3db65044a9314a97f9564fa7a1e926/tzdata-2026.3.tar.gz", hash = "sha256:4a1518b8993086a7982523e071643f3c0e5f213e75b21318e78bcabfff9d1415", size = 198674, upload-time = "2026-07-10T08:50:37.887Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b0/70/d460bd685a170790ec89317e9bd33047988e4bce507b831f5db771e142de/tzdata-2026.1-py2.py3-none-any.whl", hash = "sha256:4b1d2be7ac37ceafd7327b961aa3a54e467efbdb563a23655fbfe0d39cfc42a9", size = 348952 }, + { url = "https://files.pythonhosted.org/packages/e5/6d/b53b99a9f2766d095985947a5782f1702cabb129a34f7a802d7197af832f/tzdata-2026.3-py2.py3-none-any.whl", hash = "sha256:dc096730c87af6cab1b171c9d532be840741ff5d459015e7f6947bd7d7e54931", size = 348168, upload-time = "2026-07-10T08:50:36.46Z" }, ] [[package]] name = "urllib3" -version = "2.6.3" +version = "2.7.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/c7/24/5f1b3bdffd70275f6661c76461e25f024d5a38a46f04aaca912426a2b1d3/urllib3-2.6.3.tar.gz", hash = "sha256:1b62b6884944a57dbe321509ab94fd4d3b307075e0c2eae991ac71ee15ad38ed", size = 435556 } +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/39/08/aaaad47bc4e9dc8c725e68f9d04865dbcb2052843ff09c97b08904852d84/urllib3-2.6.3-py3-none-any.whl", hash = "sha256:bf272323e553dfb2e87d9bfd225ca7b0f467b919d7bbd355436d3fd37cb0acd4", size = 131584 }, + { 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" }, ] [[package]] name = "virtualenv" -version = "21.2.4" +version = "21.7.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "distlib" }, @@ -2271,94 +2879,168 @@ dependencies = [ { name = "platformdirs" }, { name = "python-discovery" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/0c/98/3a7e644e19cb26133488caff231be390579860bbbb3da35913c49a1d0a46/virtualenv-21.2.4.tar.gz", hash = "sha256:b294ef68192638004d72524ce7ef303e9d0cf5a44c95ce2e54a7500a6381cada", size = 5850742 } +sdist = { url = "https://files.pythonhosted.org/packages/fe/25/e367a7229b0914772ca8d81b41fde012d9feda68523b52644a571bb21ce8/virtualenv-21.7.0.tar.gz", hash = "sha256:7f9519b9432ff11b6e1a3e94061664efc2ff99ea21780e3cf4f6bd0a5da8b37c", size = 5527510, upload-time = "2026-07-21T13:12:14.109Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/27/8d/edd0bd910ff803c308ee9a6b7778621af0d10252219ad9f19ef4d4982a61/virtualenv-21.2.4-py3-none-any.whl", hash = "sha256:29d21e941795206138d0f22f4e45ff7050e5da6c6472299fb7103318763861ac", size = 5831232 }, + { url = "https://files.pythonhosted.org/packages/a5/7a/ae29312b1e88a22e81f5d21fc11526d2a114089776c2550d2b205b6c2a47/virtualenv-21.7.0-py3-none-any.whl", hash = "sha256:a8370c1c5530fbabf955e40b8fbbc68a431648b10f9433faa587db30a06e51dd", size = 5507078, upload-time = "2026-07-21T13:12:12.136Z" }, ] [[package]] name = "wheel" -version = "0.46.3" +version = "0.47.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "packaging" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/89/24/a2eb353a6edac9a0303977c4cb048134959dd2a51b48a269dfc9dde00c8a/wheel-0.46.3.tar.gz", hash = "sha256:e3e79874b07d776c40bd6033f8ddf76a7dad46a7b8aa1b2787a83083519a1803", size = 60605 } +sdist = { url = "https://files.pythonhosted.org/packages/39/62/75f18a0f03b4219c456652c7780e4d749b929eb605c098ce3a5b6b6bc081/wheel-0.47.0.tar.gz", hash = "sha256:cc72bd1009ba0cf63922e28f94d9d83b920aa2bb28f798a31d0691b02fa3c9b3", size = 63854, upload-time = "2026-04-22T15:51:27.727Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/87/22/b76d483683216dde3d67cba61fb2444be8d5be289bf628c13fc0fd90e5f9/wheel-0.46.3-py3-none-any.whl", hash = "sha256:4b399d56c9d9338230118d705d9737a2a468ccca63d5e813e2a4fc7815d8bc4d", size = 30557 }, + { url = "https://files.pythonhosted.org/packages/87/1b/9e33c09813d65e248f7f773119148a612516a4bea93e9c6f545f78455b7c/wheel-0.47.0-py3-none-any.whl", hash = "sha256:212281cab4dff978f6cedd499cd893e1f620791ca6ff7107cf270781e587eced", size = 32218, upload-time = "2026-04-22T15:51:26.296Z" }, ] [[package]] name = "wrapt" -version = "2.1.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/2e/64/925f213fdcbb9baeb1530449ac71a4d57fc361c053d06bf78d0c5c7cd80c/wrapt-2.1.2.tar.gz", hash = "sha256:3996a67eecc2c68fd47b4e3c564405a5777367adfd9b8abb58387b63ee83b21e", size = 81678 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/c7/81/60c4471fce95afa5922ca09b88a25f03c93343f759aae0f31fb4412a85c7/wrapt-2.1.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:96159a0ee2b0277d44201c3b5be479a9979cf154e8c82fa5df49586a8e7679bb", size = 60666 }, - { url = "https://files.pythonhosted.org/packages/6b/be/80e80e39e7cb90b006a0eaf11c73ac3a62bbfb3068469aec15cc0bc795de/wrapt-2.1.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:98ba61833a77b747901e9012072f038795de7fc77849f1faa965464f3f87ff2d", size = 61601 }, - { url = "https://files.pythonhosted.org/packages/b0/be/d7c88cd9293c859fc74b232abdc65a229bb953997995d6912fc85af18323/wrapt-2.1.2-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:767c0dbbe76cae2a60dd2b235ac0c87c9cccf4898aef8062e57bead46b5f6894", size = 114057 }, - { url = "https://files.pythonhosted.org/packages/ea/25/36c04602831a4d685d45a93b3abea61eca7fe35dab6c842d6f5d570ef94a/wrapt-2.1.2-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9c691a6bc752c0cc4711cc0c00896fcd0f116abc253609ef64ef930032821842", size = 116099 }, - { url = "https://files.pythonhosted.org/packages/5c/4e/98a6eb417ef551dc277bec1253d5246b25003cf36fdf3913b65cb7657a56/wrapt-2.1.2-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f3b7d73012ea75aee5844de58c88f44cf62d0d62711e39da5a82824a7c4626a8", size = 112457 }, - { url = "https://files.pythonhosted.org/packages/cb/a6/a6f7186a5297cad8ec53fd7578533b28f795fdf5372368c74bd7e6e9841c/wrapt-2.1.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:577dff354e7acd9d411eaf4bfe76b724c89c89c8fc9b7e127ee28c5f7bcb25b6", size = 115351 }, - { url = "https://files.pythonhosted.org/packages/97/6f/06e66189e721dbebd5cf20e138acc4d1150288ce118462f2fcbff92d38db/wrapt-2.1.2-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:3d7b6fd105f8b24e5bd23ccf41cb1d1099796524bcc6f7fbb8fe576c44befbc9", size = 111748 }, - { url = "https://files.pythonhosted.org/packages/ef/43/4808b86f499a51370fbdbdfa6cb91e9b9169e762716456471b619fca7a70/wrapt-2.1.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:866abdbf4612e0b34764922ef8b1c5668867610a718d3053d59e24a5e5fcfc15", size = 113783 }, - { url = "https://files.pythonhosted.org/packages/91/2c/a3f28b8fa7ac2cefa01cfcaca3471f9b0460608d012b693998cd61ef43df/wrapt-2.1.2-cp311-cp311-win32.whl", hash = "sha256:5a0a0a3a882393095573344075189eb2d566e0fd205a2b6414e9997b1b800a8b", size = 57977 }, - { url = "https://files.pythonhosted.org/packages/3f/c3/2b1c7bd07a27b1db885a2fab469b707bdd35bddf30a113b4917a7e2139d2/wrapt-2.1.2-cp311-cp311-win_amd64.whl", hash = "sha256:64a07a71d2730ba56f11d1a4b91f7817dc79bc134c11516b75d1921a7c6fcda1", size = 60336 }, - { url = "https://files.pythonhosted.org/packages/ec/5c/76ece7b401b088daa6503d6264dd80f9a727df3e6042802de9a223084ea2/wrapt-2.1.2-cp311-cp311-win_arm64.whl", hash = "sha256:b89f095fe98bc12107f82a9f7d570dc83a0870291aeb6b1d7a7d35575f55d98a", size = 58756 }, - { url = "https://files.pythonhosted.org/packages/4c/b6/1db817582c49c7fcbb7df6809d0f515af29d7c2fbf57eb44c36e98fb1492/wrapt-2.1.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:ff2aad9c4cda28a8f0653fc2d487596458c2a3f475e56ba02909e950a9efa6a9", size = 61255 }, - { url = "https://files.pythonhosted.org/packages/a2/16/9b02a6b99c09227c93cd4b73acc3678114154ec38da53043c0ddc1fba0dc/wrapt-2.1.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6433ea84e1cfacf32021d2a4ee909554ade7fd392caa6f7c13f1f4bf7b8e8748", size = 61848 }, - { url = "https://files.pythonhosted.org/packages/af/aa/ead46a88f9ec3a432a4832dfedb84092fc35af2d0ba40cd04aea3889f247/wrapt-2.1.2-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:c20b757c268d30d6215916a5fa8461048d023865d888e437fab451139cad6c8e", size = 121433 }, - { url = "https://files.pythonhosted.org/packages/3a/9f/742c7c7cdf58b59085a1ee4b6c37b013f66ac33673a7ef4aaed5e992bc33/wrapt-2.1.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:79847b83eb38e70d93dc392c7c5b587efe65b3e7afcc167aa8abd5d60e8761c8", size = 123013 }, - { url = "https://files.pythonhosted.org/packages/e8/44/2c3dd45d53236b7ed7c646fcf212251dc19e48e599debd3926b52310fafb/wrapt-2.1.2-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f8fba1bae256186a83d1875b2b1f4e2d1242e8fac0f58ec0d7e41b26967b965c", size = 117326 }, - { url = "https://files.pythonhosted.org/packages/74/e2/b17d66abc26bd96f89dec0ecd0ef03da4a1286e6ff793839ec431b9fae57/wrapt-2.1.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e3d3b35eedcf5f7d022291ecd7533321c4775f7b9cd0050a31a68499ba45757c", size = 121444 }, - { url = "https://files.pythonhosted.org/packages/3c/62/e2977843fdf9f03daf1586a0ff49060b1b2fc7ff85a7ea82b6217c1ae36e/wrapt-2.1.2-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:6f2c5390460de57fa9582bc8a1b7a6c86e1a41dfad74c5225fc07044c15cc8d1", size = 116237 }, - { url = "https://files.pythonhosted.org/packages/88/dd/27fc67914e68d740bce512f11734aec08696e6b17641fef8867c00c949fc/wrapt-2.1.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7dfa9f2cf65d027b951d05c662cc99ee3bd01f6e4691ed39848a7a5fffc902b2", size = 120563 }, - { url = "https://files.pythonhosted.org/packages/ec/9f/b750b3692ed2ef4705cb305bd68858e73010492b80e43d2a4faa5573cbe7/wrapt-2.1.2-cp312-cp312-win32.whl", hash = "sha256:eba8155747eb2cae4a0b913d9ebd12a1db4d860fc4c829d7578c7b989bd3f2f0", size = 58198 }, - { url = "https://files.pythonhosted.org/packages/8e/b2/feecfe29f28483d888d76a48f03c4c4d8afea944dbee2b0cd3380f9df032/wrapt-2.1.2-cp312-cp312-win_amd64.whl", hash = "sha256:1c51c738d7d9faa0b3601708e7e2eda9bf779e1b601dce6c77411f2a1b324a63", size = 60441 }, - { url = "https://files.pythonhosted.org/packages/44/e1/e328f605d6e208547ea9fd120804fcdec68536ac748987a68c47c606eea8/wrapt-2.1.2-cp312-cp312-win_arm64.whl", hash = "sha256:c8e46ae8e4032792eb2f677dbd0d557170a8e5524d22acc55199f43efedd39bf", size = 58836 }, - { url = "https://files.pythonhosted.org/packages/4c/7a/d936840735c828b38d26a854e85d5338894cda544cb7a85a9d5b8b9c4df7/wrapt-2.1.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:787fd6f4d67befa6fe2abdffcbd3de2d82dfc6fb8a6d850407c53332709d030b", size = 61259 }, - { url = "https://files.pythonhosted.org/packages/5e/88/9a9b9a90ac8ca11c2fdb6a286cb3a1fc7dd774c00ed70929a6434f6bc634/wrapt-2.1.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:4bdf26e03e6d0da3f0e9422fd36bcebf7bc0eeb55fdf9c727a09abc6b9fe472e", size = 61851 }, - { url = "https://files.pythonhosted.org/packages/03/a9/5b7d6a16fd6533fed2756900fc8fc923f678179aea62ada6d65c92718c00/wrapt-2.1.2-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:bbac24d879aa22998e87f6b3f481a5216311e7d53c7db87f189a7a0266dafffb", size = 121446 }, - { url = "https://files.pythonhosted.org/packages/45/bb/34c443690c847835cfe9f892be78c533d4f32366ad2888972c094a897e39/wrapt-2.1.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:16997dfb9d67addc2e3f41b62a104341e80cac52f91110dece393923c0ebd5ca", size = 123056 }, - { url = "https://files.pythonhosted.org/packages/93/b9/ff205f391cb708f67f41ea148545f2b53ff543a7ac293b30d178af4d2271/wrapt-2.1.2-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:162e4e2ba7542da9027821cb6e7c5e068d64f9a10b5f15512ea28e954893a267", size = 117359 }, - { url = "https://files.pythonhosted.org/packages/1f/3d/1ea04d7747825119c3c9a5e0874a40b33594ada92e5649347c457d982805/wrapt-2.1.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f29c827a8d9936ac320746747a016c4bc66ef639f5cd0d32df24f5eacbf9c69f", size = 121479 }, - { url = "https://files.pythonhosted.org/packages/78/cc/ee3a011920c7a023b25e8df26f306b2484a531ab84ca5c96260a73de76c0/wrapt-2.1.2-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:a9dd9813825f7ecb018c17fd147a01845eb330254dff86d3b5816f20f4d6aaf8", size = 116271 }, - { url = "https://files.pythonhosted.org/packages/98/fd/e5ff7ded41b76d802cf1191288473e850d24ba2e39a6ec540f21ae3b57cb/wrapt-2.1.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6f8dbdd3719e534860d6a78526aafc220e0241f981367018c2875178cf83a413", size = 120573 }, - { url = "https://files.pythonhosted.org/packages/47/c5/242cae3b5b080cd09bacef0591691ba1879739050cc7c801ff35c8886b66/wrapt-2.1.2-cp313-cp313-win32.whl", hash = "sha256:5c35b5d82b16a3bc6e0a04349b606a0582bc29f573786aebe98e0c159bc48db6", size = 58205 }, - { url = "https://files.pythonhosted.org/packages/12/69/c358c61e7a50f290958809b3c61ebe8b3838ea3e070d7aac9814f95a0528/wrapt-2.1.2-cp313-cp313-win_amd64.whl", hash = "sha256:f8bc1c264d8d1cf5b3560a87bbdd31131573eb25f9f9447bb6252b8d4c44a3a1", size = 60452 }, - { url = "https://files.pythonhosted.org/packages/8e/66/c8a6fcfe321295fd8c0ab1bd685b5a01462a9b3aa2f597254462fc2bc975/wrapt-2.1.2-cp313-cp313-win_arm64.whl", hash = "sha256:3beb22f674550d5634642c645aba4c72a2c66fb185ae1aebe1e955fae5a13baf", size = 58842 }, - { url = "https://files.pythonhosted.org/packages/da/55/9c7052c349106e0b3f17ae8db4b23a691a963c334de7f9dbd60f8f74a831/wrapt-2.1.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0fc04bc8664a8bc4c8e00b37b5355cffca2535209fba1abb09ae2b7c76ddf82b", size = 63075 }, - { url = "https://files.pythonhosted.org/packages/09/a8/ce7b4006f7218248dd71b7b2b732d0710845a0e49213b18faef64811ffef/wrapt-2.1.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a9b9d50c9af998875a1482a038eb05755dfd6fe303a313f6a940bb53a83c3f18", size = 63719 }, - { url = "https://files.pythonhosted.org/packages/e4/e5/2ca472e80b9e2b7a17f106bb8f9df1db11e62101652ce210f66935c6af67/wrapt-2.1.2-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:2d3ff4f0024dd224290c0eabf0240f1bfc1f26363431505fb1b0283d3b08f11d", size = 152643 }, - { url = "https://files.pythonhosted.org/packages/36/42/30f0f2cefca9d9cbf6835f544d825064570203c3e70aa873d8ae12e23791/wrapt-2.1.2-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3278c471f4468ad544a691b31bb856374fbdefb7fee1a152153e64019379f015", size = 158805 }, - { url = "https://files.pythonhosted.org/packages/bb/67/d08672f801f604889dcf58f1a0b424fe3808860ede9e03affc1876b295af/wrapt-2.1.2-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a8914c754d3134a3032601c6984db1c576e6abaf3fc68094bb8ab1379d75ff92", size = 145990 }, - { url = "https://files.pythonhosted.org/packages/68/a7/fd371b02e73babec1de6ade596e8cd9691051058cfdadbfd62a5898f3295/wrapt-2.1.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:ff95d4264e55839be37bafe1536db2ab2de19da6b65f9244f01f332b5286cfbf", size = 155670 }, - { url = "https://files.pythonhosted.org/packages/86/2d/9fe0095dfdb621009f40117dcebf41d7396c2c22dca6eac779f4c007b86c/wrapt-2.1.2-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:76405518ca4e1b76fbb1b9f686cff93aebae03920cc55ceeec48ff9f719c5f67", size = 144357 }, - { url = "https://files.pythonhosted.org/packages/0e/b6/ec7b4a254abbe4cde9fa15c5d2cca4518f6b07d0f1b77d4ee9655e30280e/wrapt-2.1.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:c0be8b5a74c5824e9359b53e7e58bef71a729bacc82e16587db1c4ebc91f7c5a", size = 150269 }, - { url = "https://files.pythonhosted.org/packages/6e/6b/2fabe8ebf148f4ee3c782aae86a795cc68ffe7d432ef550f234025ce0cfa/wrapt-2.1.2-cp313-cp313t-win32.whl", hash = "sha256:f01277d9a5fc1862f26f7626da9cf443bebc0abd2f303f41c5e995b15887dabd", size = 59894 }, - { url = "https://files.pythonhosted.org/packages/ca/fb/9ba66fc2dedc936de5f8073c0217b5d4484e966d87723415cc8262c5d9c2/wrapt-2.1.2-cp313-cp313t-win_amd64.whl", hash = "sha256:84ce8f1c2104d2f6daa912b1b5b039f331febfeee74f8042ad4e04992bd95c8f", size = 63197 }, - { url = "https://files.pythonhosted.org/packages/c0/1c/012d7423c95d0e337117723eb8ecf73c622ce15a97847e84cf3f8f26cd7e/wrapt-2.1.2-cp313-cp313t-win_arm64.whl", hash = "sha256:a93cd767e37faeddbe07d8fc4212d5cba660af59bdb0f6372c93faaa13e6e679", size = 60363 }, - { url = "https://files.pythonhosted.org/packages/39/25/e7ea0b417db02bb796182a5316398a75792cd9a22528783d868755e1f669/wrapt-2.1.2-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:1370e516598854e5b4366e09ce81e08bfe94d42b0fd569b88ec46cc56d9164a9", size = 61418 }, - { url = "https://files.pythonhosted.org/packages/ec/0f/fa539e2f6a770249907757eaeb9a5ff4deb41c026f8466c1c6d799088a9b/wrapt-2.1.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:6de1a3851c27e0bd6a04ca993ea6f80fc53e6c742ee1601f486c08e9f9b900a9", size = 61914 }, - { url = "https://files.pythonhosted.org/packages/53/37/02af1867f5b1441aaeda9c82deed061b7cd1372572ddcd717f6df90b5e93/wrapt-2.1.2-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:de9f1a2bbc5ac7f6012ec24525bdd444765a2ff64b5985ac6e0692144838542e", size = 120417 }, - { url = "https://files.pythonhosted.org/packages/c3/b7/0138a6238c8ba7476c77cf786a807f871672b37f37a422970342308276e7/wrapt-2.1.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:970d57ed83fa040d8b20c52fe74a6ae7e3775ae8cff5efd6a81e06b19078484c", size = 122797 }, - { url = "https://files.pythonhosted.org/packages/e1/ad/819ae558036d6a15b7ed290d5b14e209ca795dd4da9c58e50c067d5927b0/wrapt-2.1.2-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3969c56e4563c375861c8df14fa55146e81ac11c8db49ea6fb7f2ba58bc1ff9a", size = 117350 }, - { url = "https://files.pythonhosted.org/packages/8b/2d/afc18dc57a4600a6e594f77a9ae09db54f55ba455440a54886694a84c71b/wrapt-2.1.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:57d7c0c980abdc5f1d98b11a2aa3bb159790add80258c717fa49a99921456d90", size = 121223 }, - { url = "https://files.pythonhosted.org/packages/b9/5b/5ec189b22205697bc56eb3b62aed87a1e0423e9c8285d0781c7a83170d15/wrapt-2.1.2-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:776867878e83130c7a04237010463372e877c1c994d449ca6aaafeab6aab2586", size = 116287 }, - { url = "https://files.pythonhosted.org/packages/f7/2d/f84939a7c9b5e6cdd8a8d0f6a26cabf36a0f7e468b967720e8b0cd2bdf69/wrapt-2.1.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:fab036efe5464ec3291411fabb80a7a39e2dd80bae9bcbeeca5087fdfa891e19", size = 119593 }, - { url = "https://files.pythonhosted.org/packages/0b/fe/ccd22a1263159c4ac811ab9374c061bcb4a702773f6e06e38de5f81a1bdc/wrapt-2.1.2-cp314-cp314-win32.whl", hash = "sha256:e6ed62c82ddf58d001096ae84ce7f833db97ae2263bff31c9b336ba8cfe3f508", size = 58631 }, - { url = "https://files.pythonhosted.org/packages/65/0a/6bd83be7bff2e7efaac7b4ac9748da9d75a34634bbbbc8ad077d527146df/wrapt-2.1.2-cp314-cp314-win_amd64.whl", hash = "sha256:467e7c76315390331c67073073d00662015bb730c566820c9ca9b54e4d67fd04", size = 60875 }, - { url = "https://files.pythonhosted.org/packages/6c/c0/0b3056397fe02ff80e5a5d72d627c11eb885d1ca78e71b1a5c1e8c7d45de/wrapt-2.1.2-cp314-cp314-win_arm64.whl", hash = "sha256:da1f00a557c66225d53b095a97eace0fc5349e3bfda28fa34ffae238978ee575", size = 59164 }, - { url = "https://files.pythonhosted.org/packages/71/ed/5d89c798741993b2371396eb9d4634f009ff1ad8a6c78d366fe2883ea7a6/wrapt-2.1.2-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:62503ffbc2d3a69891cf29beeaccdb4d5e0a126e2b6a851688d4777e01428dbb", size = 63163 }, - { url = "https://files.pythonhosted.org/packages/c6/8c/05d277d182bf36b0a13d6bd393ed1dec3468a25b59d01fba2dd70fe4d6ae/wrapt-2.1.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c7e6cd120ef837d5b6f860a6ea3745f8763805c418bb2f12eeb1fa6e25f22d22", size = 63723 }, - { url = "https://files.pythonhosted.org/packages/f4/27/6c51ec1eff4413c57e72d6106bb8dec6f0c7cdba6503d78f0fa98767bcc9/wrapt-2.1.2-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:3769a77df8e756d65fbc050333f423c01ae012b4f6731aaf70cf2bef61b34596", size = 152652 }, - { url = "https://files.pythonhosted.org/packages/db/4c/d7dd662d6963fc7335bfe29d512b02b71cdfa23eeca7ab3ac74a67505deb/wrapt-2.1.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a76d61a2e851996150ba0f80582dd92a870643fa481f3b3846f229de88caf044", size = 158807 }, - { url = "https://files.pythonhosted.org/packages/b4/4d/1e5eea1a78d539d346765727422976676615814029522c76b87a95f6bcdd/wrapt-2.1.2-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:6f97edc9842cf215312b75fe737ee7c8adda75a89979f8e11558dfff6343cc4b", size = 146061 }, - { url = "https://files.pythonhosted.org/packages/89/bc/62cabea7695cd12a288023251eeefdcb8465056ddaab6227cb78a2de005b/wrapt-2.1.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:4006c351de6d5007aa33a551f600404ba44228a89e833d2fadc5caa5de8edfbf", size = 155667 }, - { url = "https://files.pythonhosted.org/packages/e9/99/6f2888cd68588f24df3a76572c69c2de28287acb9e1972bf0c83ce97dbc1/wrapt-2.1.2-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:a9372fc3639a878c8e7d87e1556fa209091b0a66e912c611e3f833e2c4202be2", size = 144392 }, - { url = "https://files.pythonhosted.org/packages/40/51/1dfc783a6c57971614c48e361a82ca3b6da9055879952587bc99fe1a7171/wrapt-2.1.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:3144b027ff30cbd2fca07c0a87e67011adb717eb5f5bd8496325c17e454257a3", size = 150296 }, - { url = "https://files.pythonhosted.org/packages/6c/38/cbb8b933a0201076c1f64fc42883b0023002bdc14a4964219154e6ff3350/wrapt-2.1.2-cp314-cp314t-win32.whl", hash = "sha256:3b8d15e52e195813efe5db8cec156eebe339aaf84222f4f4f051a6c01f237ed7", size = 60539 }, - { url = "https://files.pythonhosted.org/packages/82/dd/e5176e4b241c9f528402cebb238a36785a628179d7d8b71091154b3e4c9e/wrapt-2.1.2-cp314-cp314t-win_amd64.whl", hash = "sha256:08ffa54146a7559f5b8df4b289b46d963a8e74ed16ba3687f99896101a3990c5", size = 63969 }, - { url = "https://files.pythonhosted.org/packages/5c/99/79f17046cf67e4a95b9987ea129632ba8bcec0bc81f3fb3d19bdb0bd60cd/wrapt-2.1.2-cp314-cp314t-win_arm64.whl", hash = "sha256:72aaa9d0d8e4ed0e2e98019cea47a21f823c9dd4b43c7b77bba6679ffcca6a00", size = 60554 }, - { url = "https://files.pythonhosted.org/packages/1a/c7/8528ac2dfa2c1e6708f647df7ae144ead13f0a31146f43c7264b4942bf12/wrapt-2.1.2-py3-none-any.whl", hash = "sha256:b8fd6fa2b2c4e7621808f8c62e8317f4aae56e59721ad933bac5239d913cf0e8", size = 43993 }, +version = "2.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/2b/b0/c1f5a970721f06b85c0cd5142e0ff8fe067708abd779b0c4f4be7d61d09f/wrapt-2.3.0.tar.gz", hash = "sha256:681a2d0eefd721998f90642762b8e75c2159ec531b20ad5e437245ea7b06a107", size = 131509, upload-time = "2026-07-28T06:06:14.895Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/00/b8/9182e4c618a847be0baccb68e4602b070d0fa22c782cf058f4bc66b32709/wrapt-2.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5ab559e1b2551d23d54db2a0001c6d73bad022a254639561c5f6c382a9d6c2fe", size = 81427, upload-time = "2026-07-28T06:04:20.106Z" }, + { url = "https://files.pythonhosted.org/packages/84/ca/613cefd9c5977366b1587e61c0b428176d382e6d75b454084c5e58503042/wrapt-2.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:bff9a671bc00709cab5a7f745c592b5671873449db0ee2a569af994f16b29a4d", size = 82360, upload-time = "2026-07-28T06:04:21.613Z" }, + { url = "https://files.pythonhosted.org/packages/71/71/4cd2151a236f44a6e2dd4ed8011838d7ba0be3d656c8bafdfc65a2ed1917/wrapt-2.3.0-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:fc648a335d7e01adb3640b25f02fd0ea05886cf04d0af7f4ee902bc7b5e466e8", size = 161700, upload-time = "2026-07-28T06:04:22.723Z" }, + { url = "https://files.pythonhosted.org/packages/49/2c/bc508fee75eb2919ed69769800b09968e4aab16897f909a23f39c81e323f/wrapt-2.3.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d0077f3d65541925fa83002f967b22ad6550d24813ac64cb905f717194128d9c", size = 162922, upload-time = "2026-07-28T06:04:24.177Z" }, + { url = "https://files.pythonhosted.org/packages/4d/e5/04f34d38e66d857dfc2fc4088d60e70c0e422467822defa49b2b4a26e17b/wrapt-2.3.0-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:9790ea25190a4e0fe4cdf4eeb868e9d75f8a024a70a5b6bf9c348a3a2b72e731", size = 156125, upload-time = "2026-07-28T06:04:25.58Z" }, + { url = "https://files.pythonhosted.org/packages/23/41/c35940ea1c423f129ebe4361db853bc80d4def6326242e1206fa15bf94f4/wrapt-2.3.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:816877aa749253149f9ecfd2635d4d948ecfa338e1a0311d187b1acb1bb8a3eb", size = 162039, upload-time = "2026-07-28T06:04:27.154Z" }, + { url = "https://files.pythonhosted.org/packages/0e/60/9bda34c3d7d182aa703fe35339ae0ed4c4dad5e5c587f93890143e1f87fb/wrapt-2.3.0-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:3d1c2c1b808600d2ea808e6360910a60ed5f409a4011655e10f9164ba0a414a6", size = 155110, upload-time = "2026-07-28T06:04:28.497Z" }, + { url = "https://files.pythonhosted.org/packages/e8/ba/60bfd9b1a751f4fcb2d603668fc272d651ccdd339a56acf8c40ad21a0293/wrapt-2.3.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:5ba1e5e08ddc46130e9682b2c249f2d1dd39bda9106ed4bd401b7519f18f41bd", size = 161089, upload-time = "2026-07-28T06:04:29.959Z" }, + { url = "https://files.pythonhosted.org/packages/0f/32/2bd358c6f4f1305c813479d1e9ba746bebdd794f4a20107ab2b3ee0cbd45/wrapt-2.3.0-cp311-cp311-win32.whl", hash = "sha256:45c9279b373d15649dfa2c2077cb3408ea1a6d3125afbdab9d6b809a66f68e14", size = 78030, upload-time = "2026-07-28T06:04:31.241Z" }, + { url = "https://files.pythonhosted.org/packages/4a/62/ecc969b13b141fef89b888c9760821cb01a86ac8fc953911592c8e1e1522/wrapt-2.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:195b1842b4122fb54e3cd3dd5b2b4aa49302a5a61da901df0481f5c97aedde84", size = 80944, upload-time = "2026-07-28T06:04:32.655Z" }, + { url = "https://files.pythonhosted.org/packages/c4/3d/9278ada8a2b3f24372b630361e84e9a7de7abc3784634860c26d1c37785a/wrapt-2.3.0-cp311-cp311-win_arm64.whl", hash = "sha256:6db604ef0c67bdb2042ecdfd7b7f037cf09733557ca42360d1018285634f7b98", size = 80074, upload-time = "2026-07-28T06:04:33.811Z" }, + { url = "https://files.pythonhosted.org/packages/5b/4a/d17a0fad1bf1c5f2c887ff71fef75654141b0880bff71d157d955b5bec3a/wrapt-2.3.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:0a45ffae742ce91a16e11cb6c7cd71e7f9994f3cbd283b962ab093f5c6dcf525", size = 82139, upload-time = "2026-07-28T06:04:35.082Z" }, + { url = "https://files.pythonhosted.org/packages/6e/55/51b92daaf6defb57f4dc56bdcce985400f75c6984a03ca5e78ccac717028/wrapt-2.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:69e477046f2237ef0bc6547544ee73008dc764ca26eff44f09e976d221b34d5d", size = 82723, upload-time = "2026-07-28T06:04:36.502Z" }, + { url = "https://files.pythonhosted.org/packages/28/7f/cfd9bc4b1f5e424eeea83d0493e43f3b1b02707ce8e50c47945873982bd5/wrapt-2.3.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:5d221a6e6ddd302b8397433184e96b59f259f50024b854db1c411a881586b6b8", size = 172381, upload-time = "2026-07-28T06:04:37.674Z" }, + { url = "https://files.pythonhosted.org/packages/cb/89/ff7814f6eb6856b479946117d1138a2fbb46cdb6b1f379db359056c69743/wrapt-2.3.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:392158c9a7f2ab1b8699418bfc0fe6f83548788c418b27d7bf2019ad3405cebb", size = 174120, upload-time = "2026-07-28T06:04:38.987Z" }, + { url = "https://files.pythonhosted.org/packages/12/1e/8eded8615d39e3ce81f626937a3a87b280a2a86239a2bf14a4b4bb345034/wrapt-2.3.0-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e5301c35cf75655eb33498f2bd6ae8703ca19940e3167dc9cdf740c712a39c60", size = 163035, upload-time = "2026-07-28T06:04:40.361Z" }, + { url = "https://files.pythonhosted.org/packages/35/ea/a0af2d9da62897af2a055484920de05dade30d2ba2c0d65cbdea875d3d8b/wrapt-2.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:418f54bb09d1762db02c7009b4051149893af3153a87f92d70356703c11eea02", size = 171887, upload-time = "2026-07-28T06:04:41.614Z" }, + { url = "https://files.pythonhosted.org/packages/7e/dd/63cd4c864c65ef4906df64bd2d378f4a62b54f28063f282dfb3bf93caead/wrapt-2.3.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:1598becd30f8f2777d18564064eb4f4dbe1ab0e05a8f09786d0ef505ac782bf3", size = 161113, upload-time = "2026-07-28T06:04:42.864Z" }, + { url = "https://files.pythonhosted.org/packages/ca/ee/82f1fc9e431b5c2c5a6d201aa865dbeae3984c311c6d11a185f0c8367cf6/wrapt-2.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3da470536bf9645143323dd41b32db55c6f4304ad382094c1a1da8a92061e10d", size = 170530, upload-time = "2026-07-28T06:04:44.212Z" }, + { url = "https://files.pythonhosted.org/packages/37/a5/5dc590e863a419930d988f8b7ca3e75a6befcfb10b6003b3a152f3d5f732/wrapt-2.3.0-cp312-cp312-win32.whl", hash = "sha256:fb8e2e6704a1e0b1b989546c69e2688371ef4a07fa5f61bde3eb6211186f5ac1", size = 78323, upload-time = "2026-07-28T06:04:45.484Z" }, + { url = "https://files.pythonhosted.org/packages/51/f9/4a6925a07951df56394f7e6ebe14f69f1c5ef9d87aa63e0839acf15aa63a/wrapt-2.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:cdc021cb0b62471d6aac7f2bd92f3b4658073775f9ee7fcd325c511129e7bcc8", size = 81180, upload-time = "2026-07-28T06:04:47.021Z" }, + { url = "https://files.pythonhosted.org/packages/a8/4f/8b5de0395b2a72216751d41c9861df6facaeb611b619d8810ed2b3b23eb2/wrapt-2.3.0-cp312-cp312-win_arm64.whl", hash = "sha256:67bfe2485f50368c3fcd2275fc1fd100e350d601e0058921a7c82678a465aeab", size = 80155, upload-time = "2026-07-28T06:04:48.373Z" }, + { url = "https://files.pythonhosted.org/packages/8e/6e/0f88a072483e76b881e3fdcd6b6ffb4a5791002514fe541e72b1b73c859a/wrapt-2.3.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0d3fb71e65b001adfc42684522eeccd9c21d8ba679945abc993439567b66e59f", size = 81960, upload-time = "2026-07-28T06:04:49.622Z" }, + { url = "https://files.pythonhosted.org/packages/d7/ff/b7e2776e7c294075eb712cc9ef573d1b818f393006d09787262b8fc871c4/wrapt-2.3.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:51a7a4181c1295774812271fbcd7c909df372bc25579d4ed9eb875caaf0ae86f", size = 82435, upload-time = "2026-07-28T06:04:50.9Z" }, + { url = "https://files.pythonhosted.org/packages/d8/90/343bb5d0f1f9669bc252a6073f085b4abf862511bd5c9c9eaec754341f1d/wrapt-2.3.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:9045917809c63fdf7abe3a2ceaed3d670b8ee4500ddd9291192d30aeb34467c5", size = 170350, upload-time = "2026-07-28T06:04:52.187Z" }, + { url = "https://files.pythonhosted.org/packages/59/f8/13b79a392930bd0dd6b86cbfbfe1c40944110456e1dc6d809e5c46ece904/wrapt-2.3.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:54ca1d5573f69b5fe1d74f1f65799c68015e82f685efec9fd8cfa40a094c44d0", size = 170022, upload-time = "2026-07-28T06:04:53.599Z" }, + { url = "https://files.pythonhosted.org/packages/b2/fc/4f1b6918f5290db959d6e0c07f77385d87cede29c39c9cf8f145e9c82954/wrapt-2.3.0-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:242b60c21e30866e6a2fa606c612b47c553fa60c0eaeeeb7797fb842ac0ce609", size = 161043, upload-time = "2026-07-28T06:04:54.936Z" }, + { url = "https://files.pythonhosted.org/packages/01/e1/45d3cf74414780bdff6d0380467e003f6eb0f028b6c9403db868dbc7209c/wrapt-2.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e3f3d7ec0a51fbfe00d3aef047641ff2c58b25565b4717fc1f90e050be01cba8", size = 168576, upload-time = "2026-07-28T06:04:56.261Z" }, + { url = "https://files.pythonhosted.org/packages/f3/73/2fa58dd97f191c997755e2c6d569a68f0c433db4e4b36099bdd7227b6cac/wrapt-2.3.0-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:261f53870cd4fb2bf38f9f972c56c728fd224cb7c65721307de59d9e7e6741ae", size = 159140, upload-time = "2026-07-28T06:04:57.754Z" }, + { url = "https://files.pythonhosted.org/packages/29/a8/08a56e2000a8816d449dcbad8c8b081697acbbd490821ceca0f9d8e8d20c/wrapt-2.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8159ec0b0cb7608175eb150de94c19e34f4d47ac655f5ca9baf45df6b688ffd3", size = 169263, upload-time = "2026-07-28T06:04:59.161Z" }, + { url = "https://files.pythonhosted.org/packages/9e/d4/354e1725e35a73b2af4fa70a3e024c7a5d1bf1802dfb862dcb668aae0253/wrapt-2.3.0-cp313-cp313-win32.whl", hash = "sha256:10461884b3014fbfc8eb7d09a93c5f246363e6711d9d881f95eb8c27fdef049f", size = 78241, upload-time = "2026-07-28T06:05:00.507Z" }, + { url = "https://files.pythonhosted.org/packages/6c/7e/34c87fa2174848dfee820322aaa318bab08913998ccecc8d2f57b4ad4639/wrapt-2.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:ac870cc97b73bb00ac353329e9559a4bebc47c4c86792ed9b23b58c15b6ad838", size = 81113, upload-time = "2026-07-28T06:05:01.839Z" }, + { url = "https://files.pythonhosted.org/packages/11/86/fcc9a530579e008c9478bb565a6cdfbfd33536660f069c8b91a6607c5050/wrapt-2.3.0-cp313-cp313-win_arm64.whl", hash = "sha256:a65e8db2b4e90c2e7ade931086351c98ef420bf7a94ee08c95ac8a3cbbc43579", size = 80182, upload-time = "2026-07-28T06:05:03.152Z" }, + { url = "https://files.pythonhosted.org/packages/96/50/3864848b95b28ef73e17551fc8dccbff2628a834f52cf26a57f9c419fb83/wrapt-2.3.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:fd1f2f557dd3491fe75905e578f4db967393d40d1a8f468edc4d40ac7f2d5944", size = 83921, upload-time = "2026-07-28T06:05:04.476Z" }, + { url = "https://files.pythonhosted.org/packages/3b/4c/3d1921a60c3e8c71c540ff136e6a47a1fbccf7f671e818394889f7871d9c/wrapt-2.3.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:9f5d2aec29dfc76c37e23897dee92766a3fd4f3bff3ae7fc9c6b4bf37d8c1360", size = 84412, upload-time = "2026-07-28T06:05:05.921Z" }, + { url = "https://files.pythonhosted.org/packages/fa/1a/4a796ff7adb26ada6d4b758c94d47a38320b085e7099afc088efbbcdb006/wrapt-2.3.0-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:646d20d413ffcd1b0a2f700076e2d0252d872dcb7754860a73e45a59ea883614", size = 207168, upload-time = "2026-07-28T06:05:07.256Z" }, + { url = "https://files.pythonhosted.org/packages/1d/3e/d7777776806c579b761bac2f91721dda9f04c7a1b380213c5935cc750ae6/wrapt-2.3.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:379f670f45b7bb8993edd9f6fc36c6cc65edb81cffa0b504be34acb0303fff0a", size = 214351, upload-time = "2026-07-28T06:05:08.945Z" }, + { url = "https://files.pythonhosted.org/packages/63/27/2d64d394df7bf181955b3bb562bf33c4492fb4be113f53071106d43ad8b5/wrapt-2.3.0-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:6208f302f110295d64b22a7ac96500c791bf492dce4366e622e4912b077c9687", size = 199020, upload-time = "2026-07-28T06:05:10.418Z" }, + { url = "https://files.pythonhosted.org/packages/3e/3d/fb31d3db7d9834d265fb1a27a2adf0ddf51557c67458c97b22439ad6ae3d/wrapt-2.3.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:ed635a9ca4f3a5a2b900c10c69e823373bc00ebc114b459383596d3487da3570", size = 209969, upload-time = "2026-07-28T06:05:11.983Z" }, + { url = "https://files.pythonhosted.org/packages/1f/d1/8724b5da582e62070dc9bf4d8bf1972f317297eefd7ba1f2b5c6393ccf6c/wrapt-2.3.0-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:e3b9eaa742ae7a0aaaaad4ca4b69469d757af2d6e6663ef1dadc47adec0aeb41", size = 196324, upload-time = "2026-07-28T06:05:13.557Z" }, + { url = "https://files.pythonhosted.org/packages/0d/5c/3d9ef411149543016ee6bcf3af707f787cebd946527452b94bf122e9b7b4/wrapt-2.3.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:d0f7284f88f4833705132d06d3b425a43095c2cbd07c58166aac3ab646ba12a4", size = 202610, upload-time = "2026-07-28T06:05:15.048Z" }, + { url = "https://files.pythonhosted.org/packages/13/9b/4fc042ceb757866dd4a5fc057b3b736f2b360d3703ce9f830d83dc9226e0/wrapt-2.3.0-cp313-cp313t-win32.whl", hash = "sha256:7ebb274aba688b043429eb1500ff8a76ce0cb8ac0812ca3e301f06247b8722b3", size = 79178, upload-time = "2026-07-28T06:05:16.469Z" }, + { url = "https://files.pythonhosted.org/packages/6b/ff/b94878f8eed809ca042685276bcea9f24e8c2ca7c9653bb80bbb920a68a5/wrapt-2.3.0-cp313-cp313t-win_amd64.whl", hash = "sha256:c4bded758ad6f03b965830944a2f0bc5b2eb3767fe5a7310134315d1a6610e98", size = 82634, upload-time = "2026-07-28T06:05:18.026Z" }, + { url = "https://files.pythonhosted.org/packages/80/fb/663e1de5332a71685a729754312d327d4cada767c36e1c5a2db4c8de49e6/wrapt-2.3.0-cp313-cp313t-win_arm64.whl", hash = "sha256:d2cc64539da63e39ffb9c7ede849b6e8ddaaf7b3876b5cfb04efd85a5f3f4eb6", size = 81387, upload-time = "2026-07-28T06:05:19.417Z" }, + { url = "https://files.pythonhosted.org/packages/58/10/b073beaea89bc0d3670a75ff51139430a54b6af7ba7796507730634536dd/wrapt-2.3.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:ea52a0d0f08c584943d5764be0e84efa912c8da23c23e1e285ff2f5641c18fcc", size = 81978, upload-time = "2026-07-28T06:05:21.133Z" }, + { url = "https://files.pythonhosted.org/packages/b3/31/0916d9cebf848ed3f1a0c1888faee421747df77331e4db2bc527a9a85988/wrapt-2.3.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:fd85b0aa88efdb189d6ae2f35f4526943a8f091c38599c9c31478241c819e6a1", size = 82518, upload-time = "2026-07-28T06:05:22.562Z" }, + { url = "https://files.pythonhosted.org/packages/f5/73/31c1bf0f3384062751c2094dadb314916d70aa9b6bfd26d994b4a7b393fa/wrapt-2.3.0-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:141ed6211286a9660d8d6702de598b43f0934b4f0eda16393f100a80f501d945", size = 170187, upload-time = "2026-07-28T06:05:23.904Z" }, + { url = "https://files.pythonhosted.org/packages/ed/25/fce087d54b79b8905f3c3c9dd5f454bbd8d8acb80b960c4a6aee5b4659b3/wrapt-2.3.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2e49885a62ec4ee854d1b9e6371fda6afd219917225752abf729a3f36d4df9a5", size = 169288, upload-time = "2026-07-28T06:05:25.378Z" }, + { url = "https://files.pythonhosted.org/packages/c7/30/0d09e6dddc6b7a7230ac77f50254b5980ab4fcd22976f72f8cc8a0404458/wrapt-2.3.0-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:1d6159c9b2fefec02314e1332dbbbfaf960e369dfd26bcf7f8b258b5732065b3", size = 160932, upload-time = "2026-07-28T06:05:27.022Z" }, + { url = "https://files.pythonhosted.org/packages/2c/ca/0913af0d2ec0c43865d32d615f518fea66c13c5c930e489e9b0de248e9a8/wrapt-2.3.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:24da48596326ef8e448cfa837b454f638713d3531262375f00e5a9681682fc07", size = 169017, upload-time = "2026-07-28T06:05:28.501Z" }, + { url = "https://files.pythonhosted.org/packages/c3/f2/3d1e47ea81b822210f5df1bf942fd90780a75c055243d569b664529dea88/wrapt-2.3.0-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:cd3a2edf0427013736b8127955cec62608c56e53ea47e82812ea32059cda407f", size = 159065, upload-time = "2026-07-28T06:05:30.01Z" }, + { url = "https://files.pythonhosted.org/packages/43/a5/ef2066ced8e5fca204e2b361e9708e36555b40949c583d997ea3b590817d/wrapt-2.3.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:4fa0df3bff4e7ce45759f33fd39335fe2f60477bb9ecf7b8aa41e7d07ee36a23", size = 168821, upload-time = "2026-07-28T06:05:31.649Z" }, + { url = "https://files.pythonhosted.org/packages/d5/e1/016104650d4e572fa91506eb396b3dd8efbccc9284fdc1c9479c3d21db28/wrapt-2.3.0-cp314-cp314-win32.whl", hash = "sha256:2935d5454b3f179a29b12cf390ee47246740ba2c3a7545b1b46ba31a5f2a4a0b", size = 78700, upload-time = "2026-07-28T06:05:33.391Z" }, + { url = "https://files.pythonhosted.org/packages/3d/97/6fdc20a9f2ca304748b3f0819cbf377d55260562777bf0b615431bc3c181/wrapt-2.3.0-cp314-cp314-win_amd64.whl", hash = "sha256:cc2cea812e5cb179a796b766747e7d3b21088760d8deb95676d482b8c8e6fa7d", size = 81422, upload-time = "2026-07-28T06:05:34.774Z" }, + { url = "https://files.pythonhosted.org/packages/5e/a4/9cbd53bf05746bea2c392af39cb052427a8ec95cbd494d930733d8f44681/wrapt-2.3.0-cp314-cp314-win_arm64.whl", hash = "sha256:22cc5c0a717bd4da87018ae0bffd4c19c6fb679d3ff357216ba566ab26c76cab", size = 80639, upload-time = "2026-07-28T06:05:36.228Z" }, + { url = "https://files.pythonhosted.org/packages/43/bb/6c5e4a0f66ea0d2b2dd267e8dd05a0014eea56840b3c8595d40b0a5d1f91/wrapt-2.3.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:a6b5984cd65dd639546f0eb4b8eacf1c31cb2fe9fb5c27bffe240987cdb2cf84", size = 84030, upload-time = "2026-07-28T06:05:37.714Z" }, + { url = "https://files.pythonhosted.org/packages/6a/eb/a1aedf03283bc9cbf8a1783995ddc54e3c5a86878f19002d2c428494f4c5/wrapt-2.3.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c88abcf53daef80e01a75c7530e727fa6e2c1888fe83e3dcdba4c96216a1f5c7", size = 84419, upload-time = "2026-07-28T06:05:39.131Z" }, + { url = "https://files.pythonhosted.org/packages/63/61/50d511c0dc5105563849e86daa3e16ac7feef699f79fb05af45ea70107d5/wrapt-2.3.0-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:85de890ff968196e92dd1ae73a9fb8970495e7650a457b1c9ef0ac3dd550bce2", size = 207171, upload-time = "2026-07-28T06:05:40.69Z" }, + { url = "https://files.pythonhosted.org/packages/3f/59/9b538cf7795217e810699d16bc88b96a830d9b5c403eb2ec2db6b5f2ae81/wrapt-2.3.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:50f416b74d092bb9f41b424e90dd457f365f7ba4b11de62a23679769a21bd85c", size = 214329, upload-time = "2026-07-28T06:05:42.287Z" }, + { url = "https://files.pythonhosted.org/packages/b3/28/9935d62b1499e5c8b3d191e99ba4eb31ca237a0b699142011a837e9dc7ea/wrapt-2.3.0-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:39febbee6d77301d31da6996b152ce52452da7c7ef72aba10c2fa976dff9c295", size = 199079, upload-time = "2026-07-28T06:05:43.958Z" }, + { url = "https://files.pythonhosted.org/packages/2b/01/4446b80fa2ffa47a3449b250d004ba1c1937f07f64a179608fec735df866/wrapt-2.3.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:93513bec052c6cd987f9f580c3df068c8bc4ebae6543736be3ca7ec5959cafcd", size = 209992, upload-time = "2026-07-28T06:05:45.677Z" }, + { url = "https://files.pythonhosted.org/packages/d4/07/56f26c9f9979586a021e8148747004aba4498f49458c90b0502969b904e1/wrapt-2.3.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:729126e667da34d251b8ebf8a45ef0c5ddadc21542b3d6e1abf4259ece6508df", size = 196334, upload-time = "2026-07-28T06:05:47.608Z" }, + { url = "https://files.pythonhosted.org/packages/8b/41/6d7bcc895b0f28b2250e10908f060687b9165429dcd7f22ddb3d4c031b74/wrapt-2.3.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:626b69db2021aa01671ec7bbc9740e558522bd44c18cf2ce69bf3d666a014109", size = 202644, upload-time = "2026-07-28T06:05:49.183Z" }, + { url = "https://files.pythonhosted.org/packages/cd/25/7860927edba06b758b8852a6f02e832be715563c67a6795d94350bc81099/wrapt-2.3.0-cp314-cp314t-win32.whl", hash = "sha256:629d73378082c00a8173031f9fb30a3ac6abbc894a5bfdfae71fabc60642d501", size = 79685, upload-time = "2026-07-28T06:05:50.976Z" }, + { url = "https://files.pythonhosted.org/packages/c4/0f/270bafe92fde3b069a39bc01e39ee79340895b335640df861d43d2a51885/wrapt-2.3.0-cp314-cp314t-win_amd64.whl", hash = "sha256:42869085687f0aefd57c0f636c3f9354f8ffb321a8ba9cb52d19beb796e561c5", size = 83104, upload-time = "2026-07-28T06:05:52.405Z" }, + { url = "https://files.pythonhosted.org/packages/55/b3/af176d79a8515a8a720eccdad9a96f6e31a30abf2865430c8c42adf2fd13/wrapt-2.3.0-cp314-cp314t-win_arm64.whl", hash = "sha256:b1e5aa486e269b00ed35e64771c7d0ab8096cfd2643405ca8cd60ebedc099a51", size = 81774, upload-time = "2026-07-28T06:05:53.902Z" }, + { url = "https://files.pythonhosted.org/packages/00/39/3daf9f47be208606586de4568ba6713db53ebc8fd7a575aea1fe57983b69/wrapt-2.3.0-py3-none-any.whl", hash = "sha256:d8c7ed08477429752b8c44991f40ad7838b18332a160698740a6bfbc10d998a2", size = 61866, upload-time = "2026-07-28T06:06:12.9Z" }, +] + +[[package]] +name = "zstandard" +version = "0.25.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/fd/aa/3e0508d5a5dd96529cdc5a97011299056e14c6505b678fd58938792794b1/zstandard-0.25.0.tar.gz", hash = "sha256:7713e1179d162cf5c7906da876ec2ccb9c3a9dcbdffef0cc7f70c3667a205f0b", size = 711513, upload-time = "2025-09-14T22:15:54.002Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2a/83/c3ca27c363d104980f1c9cee1101cc8ba724ac8c28a033ede6aab89585b1/zstandard-0.25.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:933b65d7680ea337180733cf9e87293cc5500cc0eb3fc8769f4d3c88d724ec5c", size = 795254, upload-time = "2025-09-14T22:16:26.137Z" }, + { url = "https://files.pythonhosted.org/packages/ac/4d/e66465c5411a7cf4866aeadc7d108081d8ceba9bc7abe6b14aa21c671ec3/zstandard-0.25.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a3f79487c687b1fc69f19e487cd949bf3aae653d181dfb5fde3bf6d18894706f", size = 640559, upload-time = "2025-09-14T22:16:27.973Z" }, + { url = "https://files.pythonhosted.org/packages/12/56/354fe655905f290d3b147b33fe946b0f27e791e4b50a5f004c802cb3eb7b/zstandard-0.25.0-cp311-cp311-manylinux2010_i686.manylinux2014_i686.manylinux_2_12_i686.manylinux_2_17_i686.whl", hash = "sha256:0bbc9a0c65ce0eea3c34a691e3c4b6889f5f3909ba4822ab385fab9057099431", size = 5348020, upload-time = "2025-09-14T22:16:29.523Z" }, + { url = "https://files.pythonhosted.org/packages/3b/13/2b7ed68bd85e69a2069bcc72141d378f22cae5a0f3b353a2c8f50ef30c1b/zstandard-0.25.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:01582723b3ccd6939ab7b3a78622c573799d5d8737b534b86d0e06ac18dbde4a", size = 5058126, upload-time = "2025-09-14T22:16:31.811Z" }, + { url = "https://files.pythonhosted.org/packages/c9/dd/fdaf0674f4b10d92cb120ccff58bbb6626bf8368f00ebfd2a41ba4a0dc99/zstandard-0.25.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:5f1ad7bf88535edcf30038f6919abe087f606f62c00a87d7e33e7fc57cb69fcc", size = 5405390, upload-time = "2025-09-14T22:16:33.486Z" }, + { url = "https://files.pythonhosted.org/packages/0f/67/354d1555575bc2490435f90d67ca4dd65238ff2f119f30f72d5cde09c2ad/zstandard-0.25.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:06acb75eebeedb77b69048031282737717a63e71e4ae3f77cc0c3b9508320df6", size = 5452914, upload-time = "2025-09-14T22:16:35.277Z" }, + { url = "https://files.pythonhosted.org/packages/bb/1f/e9cfd801a3f9190bf3e759c422bbfd2247db9d7f3d54a56ecde70137791a/zstandard-0.25.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:9300d02ea7c6506f00e627e287e0492a5eb0371ec1670ae852fefffa6164b072", size = 5559635, upload-time = "2025-09-14T22:16:37.141Z" }, + { url = "https://files.pythonhosted.org/packages/21/88/5ba550f797ca953a52d708c8e4f380959e7e3280af029e38fbf47b55916e/zstandard-0.25.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:bfd06b1c5584b657a2892a6014c2f4c20e0db0208c159148fa78c65f7e0b0277", size = 5048277, upload-time = "2025-09-14T22:16:38.807Z" }, + { url = "https://files.pythonhosted.org/packages/46/c0/ca3e533b4fa03112facbe7fbe7779cb1ebec215688e5df576fe5429172e0/zstandard-0.25.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:f373da2c1757bb7f1acaf09369cdc1d51d84131e50d5fa9863982fd626466313", size = 5574377, upload-time = "2025-09-14T22:16:40.523Z" }, + { url = "https://files.pythonhosted.org/packages/12/9b/3fb626390113f272abd0799fd677ea33d5fc3ec185e62e6be534493c4b60/zstandard-0.25.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6c0e5a65158a7946e7a7affa6418878ef97ab66636f13353b8502d7ea03c8097", size = 4961493, upload-time = "2025-09-14T22:16:43.3Z" }, + { url = "https://files.pythonhosted.org/packages/cb/d3/23094a6b6a4b1343b27ae68249daa17ae0651fcfec9ed4de09d14b940285/zstandard-0.25.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:c8e167d5adf59476fa3e37bee730890e389410c354771a62e3c076c86f9f7778", size = 5269018, upload-time = "2025-09-14T22:16:45.292Z" }, + { url = "https://files.pythonhosted.org/packages/8c/a7/bb5a0c1c0f3f4b5e9d5b55198e39de91e04ba7c205cc46fcb0f95f0383c1/zstandard-0.25.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:98750a309eb2f020da61e727de7d7ba3c57c97cf6213f6f6277bb7fb42a8e065", size = 5443672, upload-time = "2025-09-14T22:16:47.076Z" }, + { url = "https://files.pythonhosted.org/packages/27/22/503347aa08d073993f25109c36c8d9f029c7d5949198050962cb568dfa5e/zstandard-0.25.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:22a086cff1b6ceca18a8dd6096ec631e430e93a8e70a9ca5efa7561a00f826fa", size = 5822753, upload-time = "2025-09-14T22:16:49.316Z" }, + { url = "https://files.pythonhosted.org/packages/e2/be/94267dc6ee64f0f8ba2b2ae7c7a2df934a816baaa7291db9e1aa77394c3c/zstandard-0.25.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:72d35d7aa0bba323965da807a462b0966c91608ef3a48ba761678cb20ce5d8b7", size = 5366047, upload-time = "2025-09-14T22:16:51.328Z" }, + { url = "https://files.pythonhosted.org/packages/7b/a3/732893eab0a3a7aecff8b99052fecf9f605cf0fb5fb6d0290e36beee47a4/zstandard-0.25.0-cp311-cp311-win32.whl", hash = "sha256:f5aeea11ded7320a84dcdd62a3d95b5186834224a9e55b92ccae35d21a8b63d4", size = 436484, upload-time = "2025-09-14T22:16:55.005Z" }, + { url = "https://files.pythonhosted.org/packages/43/a3/c6155f5c1cce691cb80dfd38627046e50af3ee9ddc5d0b45b9b063bfb8c9/zstandard-0.25.0-cp311-cp311-win_amd64.whl", hash = "sha256:daab68faadb847063d0c56f361a289c4f268706b598afbf9ad113cbe5c38b6b2", size = 506183, upload-time = "2025-09-14T22:16:52.753Z" }, + { url = "https://files.pythonhosted.org/packages/8c/3e/8945ab86a0820cc0e0cdbf38086a92868a9172020fdab8a03ac19662b0e5/zstandard-0.25.0-cp311-cp311-win_arm64.whl", hash = "sha256:22a06c5df3751bb7dc67406f5374734ccee8ed37fc5981bf1ad7041831fa1137", size = 462533, upload-time = "2025-09-14T22:16:53.878Z" }, + { url = "https://files.pythonhosted.org/packages/82/fc/f26eb6ef91ae723a03e16eddb198abcfce2bc5a42e224d44cc8b6765e57e/zstandard-0.25.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7b3c3a3ab9daa3eed242d6ecceead93aebbb8f5f84318d82cee643e019c4b73b", size = 795738, upload-time = "2025-09-14T22:16:56.237Z" }, + { url = "https://files.pythonhosted.org/packages/aa/1c/d920d64b22f8dd028a8b90e2d756e431a5d86194caa78e3819c7bf53b4b3/zstandard-0.25.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:913cbd31a400febff93b564a23e17c3ed2d56c064006f54efec210d586171c00", size = 640436, upload-time = "2025-09-14T22:16:57.774Z" }, + { url = "https://files.pythonhosted.org/packages/53/6c/288c3f0bd9fcfe9ca41e2c2fbfd17b2097f6af57b62a81161941f09afa76/zstandard-0.25.0-cp312-cp312-manylinux2010_i686.manylinux2014_i686.manylinux_2_12_i686.manylinux_2_17_i686.whl", hash = "sha256:011d388c76b11a0c165374ce660ce2c8efa8e5d87f34996aa80f9c0816698b64", size = 5343019, upload-time = "2025-09-14T22:16:59.302Z" }, + { url = "https://files.pythonhosted.org/packages/1e/15/efef5a2f204a64bdb5571e6161d49f7ef0fffdbca953a615efbec045f60f/zstandard-0.25.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:6dffecc361d079bb48d7caef5d673c88c8988d3d33fb74ab95b7ee6da42652ea", size = 5063012, upload-time = "2025-09-14T22:17:01.156Z" }, + { url = "https://files.pythonhosted.org/packages/b7/37/a6ce629ffdb43959e92e87ebdaeebb5ac81c944b6a75c9c47e300f85abdf/zstandard-0.25.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:7149623bba7fdf7e7f24312953bcf73cae103db8cae49f8154dd1eadc8a29ecb", size = 5394148, upload-time = "2025-09-14T22:17:03.091Z" }, + { url = "https://files.pythonhosted.org/packages/e3/79/2bf870b3abeb5c070fe2d670a5a8d1057a8270f125ef7676d29ea900f496/zstandard-0.25.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:6a573a35693e03cf1d67799fd01b50ff578515a8aeadd4595d2a7fa9f3ec002a", size = 5451652, upload-time = "2025-09-14T22:17:04.979Z" }, + { url = "https://files.pythonhosted.org/packages/53/60/7be26e610767316c028a2cbedb9a3beabdbe33e2182c373f71a1c0b88f36/zstandard-0.25.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:5a56ba0db2d244117ed744dfa8f6f5b366e14148e00de44723413b2f3938a902", size = 5546993, upload-time = "2025-09-14T22:17:06.781Z" }, + { url = "https://files.pythonhosted.org/packages/85/c7/3483ad9ff0662623f3648479b0380d2de5510abf00990468c286c6b04017/zstandard-0.25.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:10ef2a79ab8e2974e2075fb984e5b9806c64134810fac21576f0668e7ea19f8f", size = 5046806, upload-time = "2025-09-14T22:17:08.415Z" }, + { url = "https://files.pythonhosted.org/packages/08/b3/206883dd25b8d1591a1caa44b54c2aad84badccf2f1de9e2d60a446f9a25/zstandard-0.25.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:aaf21ba8fb76d102b696781bddaa0954b782536446083ae3fdaa6f16b25a1c4b", size = 5576659, upload-time = "2025-09-14T22:17:10.164Z" }, + { url = "https://files.pythonhosted.org/packages/9d/31/76c0779101453e6c117b0ff22565865c54f48f8bd807df2b00c2c404b8e0/zstandard-0.25.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1869da9571d5e94a85a5e8d57e4e8807b175c9e4a6294e3b66fa4efb074d90f6", size = 4953933, upload-time = "2025-09-14T22:17:11.857Z" }, + { url = "https://files.pythonhosted.org/packages/18/e1/97680c664a1bf9a247a280a053d98e251424af51f1b196c6d52f117c9720/zstandard-0.25.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:809c5bcb2c67cd0ed81e9229d227d4ca28f82d0f778fc5fea624a9def3963f91", size = 5268008, upload-time = "2025-09-14T22:17:13.627Z" }, + { url = "https://files.pythonhosted.org/packages/1e/73/316e4010de585ac798e154e88fd81bb16afc5c5cb1a72eeb16dd37e8024a/zstandard-0.25.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:f27662e4f7dbf9f9c12391cb37b4c4c3cb90ffbd3b1fb9284dadbbb8935fa708", size = 5433517, upload-time = "2025-09-14T22:17:16.103Z" }, + { url = "https://files.pythonhosted.org/packages/5b/60/dd0f8cfa8129c5a0ce3ea6b7f70be5b33d2618013a161e1ff26c2b39787c/zstandard-0.25.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:99c0c846e6e61718715a3c9437ccc625de26593fea60189567f0118dc9db7512", size = 5814292, upload-time = "2025-09-14T22:17:17.827Z" }, + { url = "https://files.pythonhosted.org/packages/fc/5f/75aafd4b9d11b5407b641b8e41a57864097663699f23e9ad4dbb91dc6bfe/zstandard-0.25.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:474d2596a2dbc241a556e965fb76002c1ce655445e4e3bf38e5477d413165ffa", size = 5360237, upload-time = "2025-09-14T22:17:19.954Z" }, + { url = "https://files.pythonhosted.org/packages/ff/8d/0309daffea4fcac7981021dbf21cdb2e3427a9e76bafbcdbdf5392ff99a4/zstandard-0.25.0-cp312-cp312-win32.whl", hash = "sha256:23ebc8f17a03133b4426bcc04aabd68f8236eb78c3760f12783385171b0fd8bd", size = 436922, upload-time = "2025-09-14T22:17:24.398Z" }, + { url = "https://files.pythonhosted.org/packages/79/3b/fa54d9015f945330510cb5d0b0501e8253c127cca7ebe8ba46a965df18c5/zstandard-0.25.0-cp312-cp312-win_amd64.whl", hash = "sha256:ffef5a74088f1e09947aecf91011136665152e0b4b359c42be3373897fb39b01", size = 506276, upload-time = "2025-09-14T22:17:21.429Z" }, + { url = "https://files.pythonhosted.org/packages/ea/6b/8b51697e5319b1f9ac71087b0af9a40d8a6288ff8025c36486e0c12abcc4/zstandard-0.25.0-cp312-cp312-win_arm64.whl", hash = "sha256:181eb40e0b6a29b3cd2849f825e0fa34397f649170673d385f3598ae17cca2e9", size = 462679, upload-time = "2025-09-14T22:17:23.147Z" }, + { url = "https://files.pythonhosted.org/packages/35/0b/8df9c4ad06af91d39e94fa96cc010a24ac4ef1378d3efab9223cc8593d40/zstandard-0.25.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ec996f12524f88e151c339688c3897194821d7f03081ab35d31d1e12ec975e94", size = 795735, upload-time = "2025-09-14T22:17:26.042Z" }, + { url = "https://files.pythonhosted.org/packages/3f/06/9ae96a3e5dcfd119377ba33d4c42a7d89da1efabd5cb3e366b156c45ff4d/zstandard-0.25.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a1a4ae2dec3993a32247995bdfe367fc3266da832d82f8438c8570f989753de1", size = 640440, upload-time = "2025-09-14T22:17:27.366Z" }, + { url = "https://files.pythonhosted.org/packages/d9/14/933d27204c2bd404229c69f445862454dcc101cd69ef8c6068f15aaec12c/zstandard-0.25.0-cp313-cp313-manylinux2010_i686.manylinux2014_i686.manylinux_2_12_i686.manylinux_2_17_i686.whl", hash = "sha256:e96594a5537722fdfb79951672a2a63aec5ebfb823e7560586f7484819f2a08f", size = 5343070, upload-time = "2025-09-14T22:17:28.896Z" }, + { url = "https://files.pythonhosted.org/packages/6d/db/ddb11011826ed7db9d0e485d13df79b58586bfdec56e5c84a928a9a78c1c/zstandard-0.25.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:bfc4e20784722098822e3eee42b8e576b379ed72cca4a7cb856ae733e62192ea", size = 5063001, upload-time = "2025-09-14T22:17:31.044Z" }, + { url = "https://files.pythonhosted.org/packages/db/00/87466ea3f99599d02a5238498b87bf84a6348290c19571051839ca943777/zstandard-0.25.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:457ed498fc58cdc12fc48f7950e02740d4f7ae9493dd4ab2168a47c93c31298e", size = 5394120, upload-time = "2025-09-14T22:17:32.711Z" }, + { url = "https://files.pythonhosted.org/packages/2b/95/fc5531d9c618a679a20ff6c29e2b3ef1d1f4ad66c5e161ae6ff847d102a9/zstandard-0.25.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:fd7a5004eb1980d3cefe26b2685bcb0b17989901a70a1040d1ac86f1d898c551", size = 5451230, upload-time = "2025-09-14T22:17:34.41Z" }, + { url = "https://files.pythonhosted.org/packages/63/4b/e3678b4e776db00f9f7b2fe58e547e8928ef32727d7a1ff01dea010f3f13/zstandard-0.25.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8e735494da3db08694d26480f1493ad2cf86e99bdd53e8e9771b2752a5c0246a", size = 5547173, upload-time = "2025-09-14T22:17:36.084Z" }, + { url = "https://files.pythonhosted.org/packages/4e/d5/ba05ed95c6b8ec30bd468dfeab20589f2cf709b5c940483e31d991f2ca58/zstandard-0.25.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:3a39c94ad7866160a4a46d772e43311a743c316942037671beb264e395bdd611", size = 5046736, upload-time = "2025-09-14T22:17:37.891Z" }, + { url = "https://files.pythonhosted.org/packages/50/d5/870aa06b3a76c73eced65c044b92286a3c4e00554005ff51962deef28e28/zstandard-0.25.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:172de1f06947577d3a3005416977cce6168f2261284c02080e7ad0185faeced3", size = 5576368, upload-time = "2025-09-14T22:17:40.206Z" }, + { url = "https://files.pythonhosted.org/packages/5d/35/398dc2ffc89d304d59bc12f0fdd931b4ce455bddf7038a0a67733a25f550/zstandard-0.25.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:3c83b0188c852a47cd13ef3bf9209fb0a77fa5374958b8c53aaa699398c6bd7b", size = 4954022, upload-time = "2025-09-14T22:17:41.879Z" }, + { url = "https://files.pythonhosted.org/packages/9a/5c/36ba1e5507d56d2213202ec2b05e8541734af5f2ce378c5d1ceaf4d88dc4/zstandard-0.25.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:1673b7199bbe763365b81a4f3252b8e80f44c9e323fc42940dc8843bfeaf9851", size = 5267889, upload-time = "2025-09-14T22:17:43.577Z" }, + { url = "https://files.pythonhosted.org/packages/70/e8/2ec6b6fb7358b2ec0113ae202647ca7c0e9d15b61c005ae5225ad0995df5/zstandard-0.25.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:0be7622c37c183406f3dbf0cba104118eb16a4ea7359eeb5752f0794882fc250", size = 5433952, upload-time = "2025-09-14T22:17:45.271Z" }, + { url = "https://files.pythonhosted.org/packages/7b/01/b5f4d4dbc59ef193e870495c6f1275f5b2928e01ff5a81fecb22a06e22fb/zstandard-0.25.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:5f5e4c2a23ca271c218ac025bd7d635597048b366d6f31f420aaeb715239fc98", size = 5814054, upload-time = "2025-09-14T22:17:47.08Z" }, + { url = "https://files.pythonhosted.org/packages/b2/e5/fbd822d5c6f427cf158316d012c5a12f233473c2f9c5fe5ab1ae5d21f3d8/zstandard-0.25.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4f187a0bb61b35119d1926aee039524d1f93aaf38a9916b8c4b78ac8514a0aaf", size = 5360113, upload-time = "2025-09-14T22:17:48.893Z" }, + { url = "https://files.pythonhosted.org/packages/8e/e0/69a553d2047f9a2c7347caa225bb3a63b6d7704ad74610cb7823baa08ed7/zstandard-0.25.0-cp313-cp313-win32.whl", hash = "sha256:7030defa83eef3e51ff26f0b7bfb229f0204b66fe18e04359ce3474ac33cbc09", size = 436936, upload-time = "2025-09-14T22:17:52.658Z" }, + { url = "https://files.pythonhosted.org/packages/d9/82/b9c06c870f3bd8767c201f1edbdf9e8dc34be5b0fbc5682c4f80fe948475/zstandard-0.25.0-cp313-cp313-win_amd64.whl", hash = "sha256:1f830a0dac88719af0ae43b8b2d6aef487d437036468ef3c2ea59c51f9d55fd5", size = 506232, upload-time = "2025-09-14T22:17:50.402Z" }, + { url = "https://files.pythonhosted.org/packages/d4/57/60c3c01243bb81d381c9916e2a6d9e149ab8627c0c7d7abb2d73384b3c0c/zstandard-0.25.0-cp313-cp313-win_arm64.whl", hash = "sha256:85304a43f4d513f5464ceb938aa02c1e78c2943b29f44a750b48b25ac999a049", size = 462671, upload-time = "2025-09-14T22:17:51.533Z" }, + { url = "https://files.pythonhosted.org/packages/3d/5c/f8923b595b55fe49e30612987ad8bf053aef555c14f05bb659dd5dbe3e8a/zstandard-0.25.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:e29f0cf06974c899b2c188ef7f783607dbef36da4c242eb6c82dcd8b512855e3", size = 795887, upload-time = "2025-09-14T22:17:54.198Z" }, + { url = "https://files.pythonhosted.org/packages/8d/09/d0a2a14fc3439c5f874042dca72a79c70a532090b7ba0003be73fee37ae2/zstandard-0.25.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:05df5136bc5a011f33cd25bc9f506e7426c0c9b3f9954f056831ce68f3b6689f", size = 640658, upload-time = "2025-09-14T22:17:55.423Z" }, + { url = "https://files.pythonhosted.org/packages/5d/7c/8b6b71b1ddd517f68ffb55e10834388d4f793c49c6b83effaaa05785b0b4/zstandard-0.25.0-cp314-cp314-manylinux2010_i686.manylinux_2_12_i686.manylinux_2_28_i686.whl", hash = "sha256:f604efd28f239cc21b3adb53eb061e2a205dc164be408e553b41ba2ffe0ca15c", size = 5379849, upload-time = "2025-09-14T22:17:57.372Z" }, + { url = "https://files.pythonhosted.org/packages/a4/86/a48e56320d0a17189ab7a42645387334fba2200e904ee47fc5a26c1fd8ca/zstandard-0.25.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:223415140608d0f0da010499eaa8ccdb9af210a543fac54bce15babbcfc78439", size = 5058095, upload-time = "2025-09-14T22:17:59.498Z" }, + { url = "https://files.pythonhosted.org/packages/f8/ad/eb659984ee2c0a779f9d06dbfe45e2dc39d99ff40a319895df2d3d9a48e5/zstandard-0.25.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2e54296a283f3ab5a26fc9b8b5d4978ea0532f37b231644f367aa588930aa043", size = 5551751, upload-time = "2025-09-14T22:18:01.618Z" }, + { url = "https://files.pythonhosted.org/packages/61/b3/b637faea43677eb7bd42ab204dfb7053bd5c4582bfe6b1baefa80ac0c47b/zstandard-0.25.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ca54090275939dc8ec5dea2d2afb400e0f83444b2fc24e07df7fdef677110859", size = 6364818, upload-time = "2025-09-14T22:18:03.769Z" }, + { url = "https://files.pythonhosted.org/packages/31/dc/cc50210e11e465c975462439a492516a73300ab8caa8f5e0902544fd748b/zstandard-0.25.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e09bb6252b6476d8d56100e8147b803befa9a12cea144bbe629dd508800d1ad0", size = 5560402, upload-time = "2025-09-14T22:18:05.954Z" }, + { url = "https://files.pythonhosted.org/packages/c9/ae/56523ae9c142f0c08efd5e868a6da613ae76614eca1305259c3bf6a0ed43/zstandard-0.25.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:a9ec8c642d1ec73287ae3e726792dd86c96f5681eb8df274a757bf62b750eae7", size = 4955108, upload-time = "2025-09-14T22:18:07.68Z" }, + { url = "https://files.pythonhosted.org/packages/98/cf/c899f2d6df0840d5e384cf4c4121458c72802e8bda19691f3b16619f51e9/zstandard-0.25.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:a4089a10e598eae6393756b036e0f419e8c1d60f44a831520f9af41c14216cf2", size = 5269248, upload-time = "2025-09-14T22:18:09.753Z" }, + { url = "https://files.pythonhosted.org/packages/1b/c0/59e912a531d91e1c192d3085fc0f6fb2852753c301a812d856d857ea03c6/zstandard-0.25.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:f67e8f1a324a900e75b5e28ffb152bcac9fbed1cc7b43f99cd90f395c4375344", size = 5430330, upload-time = "2025-09-14T22:18:11.966Z" }, + { url = "https://files.pythonhosted.org/packages/a0/1d/7e31db1240de2df22a58e2ea9a93fc6e38cc29353e660c0272b6735d6669/zstandard-0.25.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:9654dbc012d8b06fc3d19cc825af3f7bf8ae242226df5f83936cb39f5fdc846c", size = 5811123, upload-time = "2025-09-14T22:18:13.907Z" }, + { url = "https://files.pythonhosted.org/packages/f6/49/fac46df5ad353d50535e118d6983069df68ca5908d4d65b8c466150a4ff1/zstandard-0.25.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:4203ce3b31aec23012d3a4cf4a2ed64d12fea5269c49aed5e4c3611b938e4088", size = 5359591, upload-time = "2025-09-14T22:18:16.465Z" }, + { url = "https://files.pythonhosted.org/packages/c2/38/f249a2050ad1eea0bb364046153942e34abba95dd5520af199aed86fbb49/zstandard-0.25.0-cp314-cp314-win32.whl", hash = "sha256:da469dc041701583e34de852d8634703550348d5822e66a0c827d39b05365b12", size = 444513, upload-time = "2025-09-14T22:18:20.61Z" }, + { url = "https://files.pythonhosted.org/packages/3a/43/241f9615bcf8ba8903b3f0432da069e857fc4fd1783bd26183db53c4804b/zstandard-0.25.0-cp314-cp314-win_amd64.whl", hash = "sha256:c19bcdd826e95671065f8692b5a4aa95c52dc7a02a4c5a0cac46deb879a017a2", size = 516118, upload-time = "2025-09-14T22:18:17.849Z" }, + { url = "https://files.pythonhosted.org/packages/f0/ef/da163ce2450ed4febf6467d77ccb4cd52c4c30ab45624bad26ca0a27260c/zstandard-0.25.0-cp314-cp314-win_arm64.whl", hash = "sha256:d7541afd73985c630bafcd6338d2518ae96060075f9463d7dc14cfb33514383d", size = 476940, upload-time = "2025-09-14T22:18:19.088Z" }, ]