diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 00000000..a63d738e --- /dev/null +++ b/.dockerignore @@ -0,0 +1,3 @@ +.venv +**/__pycache__ +**/*.pyc diff --git a/.gitignore b/.gitignore index 1797505c..e7b7ec1b 100644 --- a/.gitignore +++ b/.gitignore @@ -2,9 +2,9 @@ **.swp **.pyc **.DS_Store -uv.lock artifacts build devel provision/ansible/.password .catkin_tools +.venv diff --git a/CHANGELOG.md b/CHANGELOG.md index f7319a1a..1e6c4b6d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,24 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added + +- `kamera-calibrate`: multi-sensor rig calibration from a calibration flight + (`kamera/calibration`). One COLMAP model with trigger-synchronized frames, INS + position priors, rig bundle adjustment; writes camera model yamls, `rig.yaml`, + DIVE v2 registration JSON, GIFs and a PDF report. + +### Changed + +- Post-processing env moves to Python 3.13 and pycolmap 4.2 (conda-forge, CUDA build). +- `bootstrap.py` builds the conda env and `.venv` in one step on Linux, macOS and + Windows; `make install` calls it. `.venv` is recreated instead of failing when it exists. +- ruff (line length 88) is the project formatter and linter, installed with the `dev` group. + +### Removed + +- Old per-camera calibration scripts under `kamera/postflight/scripts`. + ## [0.5.0] - 2026-07-21 ### Added diff --git a/Makefile b/Makefile index 0d0026fd..9ca67349 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,11 @@ ROS_DISTRO ?= noetic +CONDA_ENV ?= kamera -.PHONY: build core viame gui postflight follower leader all clean +.PHONY: install build core viame gui postflight follower leader all clean + +# Conda env from environment.yml plus .venv on top of it (see bootstrap.py) +install: + @python bootstrap.py --name $(CONDA_ENV) build: docker compose build diff --git a/README.md b/README.md index 0cc85a39..53deb41e 100644 --- a/README.md +++ b/README.md @@ -22,11 +22,41 @@ KAMERA, or the **K**nowledge-guided Image **A**cquisition **M**anag**ER** and ** ## Installation +### Post-processing (native, Windows or Linux) + +GDAL and pycolmap come from conda-forge (Python 3.13); [uv](https://docs.astral.sh/uv/) +installs the rest into `.venv` from the lockfile. Requires conda: +[Miniforge](https://conda-forge.org/download/) is recommended since it defaults to the +conda-forge channel these packages come from, but +[Miniconda](https://www.anaconda.com/download/success) or a full Anaconda install also +work because `environment.yml` pins the channel. The same steps work on Linux, macOS +and Windows (PowerShell or Miniforge/Anaconda Prompt): + ```bash git clone https://github.com/Kitware/kamera.git cd kamera -# For the pure post-processing and generating flight summary, you can install -# the requirements in requirements.txt, or use the provided dockerfile +python bootstrap.py +conda activate kamera +source .venv/bin/activate # Windows: .venv\Scripts\activate +``` + +`bootstrap.py` creates the `kamera` conda env from `environment.yml` (or updates it +if it exists) and builds `.venv` on top of it; `make install` does the same on Linux. +Pass `--name` to build a second env beside an existing one. Afterwards, activating +the conda env and then `.venv` is all you need. Conda installs the CUDA build of +pycolmap automatically with NVIDIA driver 575+ (CUDA 12.9), otherwise the CPU build; +GPU only matters for full camera model calibration. + +### Rig calibration + +`kamera-calibrate ` calibrates every camera on the rig from a calibration +flight and writes camera models, the rig geometry, DIVE registration files and a PDF +report. See [kamera/calibration/README.md](kamera/calibration/README.md). + +### Docker images + +```bash +# post-processing / flight summary image make postflight # Builds the core docker images for use in the onboard sytems make nuvo diff --git a/bootstrap.py b/bootstrap.py new file mode 100644 index 00000000..63d8db4a --- /dev/null +++ b/bootstrap.py @@ -0,0 +1,108 @@ +"""Build the post-processing environment on Linux, macOS or Windows. + +Creates (or updates) the conda env from environment.yml, then builds .venv on top +of it with uv from the lockfile. Run it with any Python, e.g. the conda base one: + + python bootstrap.py # env named as in environment.yml + python bootstrap.py --name test # a second env beside it + +Afterwards activate the conda env and then .venv (the script prints the commands). +""" + +from __future__ import annotations + +import argparse +import os +import re +import shutil +import subprocess +import sys + +ROOT = os.path.dirname(os.path.abspath(__file__)) +ENV_FILE = os.path.join(ROOT, "environment.yml") + + +def read_environment_yml() -> tuple[str, str]: + """Return (env name, python version) without needing pyyaml.""" + text = open(ENV_FILE).read() + name = re.search(r"^name:\s*(\S+)", text, re.M) + python = re.search(r"^\s*-\s*python\s*=\s*([\d.]+)", text, re.M) + if not name or not python: + sys.exit(f"could not read name and python version from {ENV_FILE}") + return name.group(1), python.group(1) + + +def find_conda() -> str: + """CONDA_EXE if it still points at a real conda (a shell can carry a stale one + after an uninstall), else whatever conda is on PATH, else micromamba (the docker + image has nothing else).""" + conda = os.environ.get("CONDA_EXE", "") + if not os.path.isfile(conda): + conda = shutil.which("conda") + if not conda: + conda = os.environ.get("MAMBA_EXE", "") + if not os.path.isfile(conda): + conda = shutil.which("micromamba") + if not conda: + sys.exit( + "conda not found; install Miniforge from https://conda-forge.org/download/" + ) + return conda + + +def is_micromamba(conda: str) -> bool: + return "micromamba" in os.path.basename(conda).lower() + + +def run(cmd: list[str], dry_run: bool) -> None: + print("+", " ".join(cmd), flush=True) + if not dry_run: + subprocess.run(cmd, cwd=ROOT, check=True) + + +def conda_env_exists(conda: str, name: str) -> bool: + """Ask this conda whether it can resolve the env by name (a path match is not + enough when several conda installs share a machine).""" + probe = [conda, "run", "-n", name, "python", "--version"] + return subprocess.run(probe, capture_output=True).returncode == 0 + + +def main() -> None: + default_name, python_version = read_environment_yml() + parser = argparse.ArgumentParser(description=__doc__.split("\n")[0]) + parser.add_argument("--name", default=default_name, help="conda env name") + parser.add_argument( + "--dry-run", action="store_true", help="print the commands without running" + ) + args = parser.parse_args() + + conda = find_conda() + # micromamba prompts before installing unless told not to; conda env does not. + yes = ["-y"] if is_micromamba(conda) else [] + verb = "update" if conda_env_exists(conda, args.name) else "create" + run([conda, "env", verb, "-n", args.name, "-f", ENV_FILE] + yes, args.dry_run) + + # uv runs inside the conda env so .venv is built on the conda python and sees + # the conda GDAL and pycolmap through --system-site-packages. micromamba run + # never captures output and rejects conda's flag for that. + stream = [] if is_micromamba(conda) else ["--no-capture-output"] + uv = [conda, "run", "-n", args.name] + stream + ["uv"] + run( + uv + + ["venv", "--clear", "--system-site-packages", f"--python={python_version}"], + args.dry_run, + ) + run(uv + ["sync", "--frozen", "--no-cache"], args.dry_run) + + activate = ( + r".venv\Scripts\activate" if os.name == "nt" else "source .venv/bin/activate" + ) + tool = "micromamba" if is_micromamba(conda) else "conda" + print( + "\nInstallation finished. To use kamera:" + f"\n {tool} activate {args.name}\n {activate}" + ) + + +if __name__ == "__main__": + main() diff --git a/docker/gui.dockerfile b/docker/gui.dockerfile index f46b5651..fdbf0e66 100644 --- a/docker/gui.dockerfile +++ b/docker/gui.dockerfile @@ -36,11 +36,11 @@ RUN mkdir -p /home/user/.config/kamera && \ RUN ln -sv /usr/bin/python3 /usr/bin/python || true RUN find /home/user -not -user user -execdir chown user {} \+ -# Install kamera for wxpython_gui imports (e.g. colmap_processing.camera_models). -# Use --no-deps: base images already provide runtime deps, and a full install -# fails trying to replace distutils-installed PyYAML from ROS/Noetic. +# Install kamera for wxpython_gui imports. --no-deps: deps come from the base +# image (a full install trips on ROS's distutils PyYAML). +# --ignore-requires-python: ROS Noetic pins python 3.8, below our 3.10 floor. RUN pip install --no-cache-dir matplotlib \ - && pip install --no-cache-dir --no-deps -e $REPO_DIR + && pip install --no-cache-dir --no-deps --ignore-requires-python -e $REPO_DIR # use the exec form of run because we need bash syntax USER user diff --git a/docker/kamerapy.dockerfile b/docker/kamerapy.dockerfile index e00f1bf7..65469fe7 100644 --- a/docker/kamerapy.dockerfile +++ b/docker/kamerapy.dockerfile @@ -1,20 +1,41 @@ -FROM python:3.10.15-bookworm +FROM debian:bookworm-slim + +SHELL ["/bin/bash", "-c"] RUN apt-get update && apt-get install -yq \ - libgdal-dev \ - python3-gdal \ - libgl1-mesa-glx \ + curl \ + bzip2 \ + ca-certificates \ + make \ + libgl1 \ + libglib2.0-0 \ libsm6 \ libxext6 \ redis \ dnsutils \ - gdal-bin + && rm -rf /var/lib/apt/lists/* + +# Install micromamba +ARG MAMBA_VERSION=2.3.3 +RUN curl -Ls https://micro.mamba.pm/api/micromamba/linux-64/${MAMBA_VERSION} \ + | tar -xvj -C /usr/local/bin --strip-components=1 bin/micromamba +ENV MAMBA_ROOT_PREFIX=/opt/conda + +# Conda env supplies python + GDAL + uv; make install layers .venv on top +COPY environment.yml /tmp/environment.yml +RUN micromamba create -y -n kamera -f /tmp/environment.yml \ + && micromamba clean --all -y -RUN pip install --upgrade pip -RUN pip install setuptools==57.0.0 +ENV UV_COMPILE_BYTECODE=1 \ + UV_LINK_MODE=copy COPY ./ /src/kamera WORKDIR /src/kamera -RUN pip install -e . + +RUN eval "$(micromamba shell hook --shell bash)" \ + && micromamba activate kamera \ + && make install + +ENV PATH="/src/kamera/.venv/bin:/opt/conda/envs/kamera/bin:$PATH" ENTRYPOINT ["bash"] diff --git a/environment.yml b/environment.yml new file mode 100644 index 00000000..62d7b891 --- /dev/null +++ b/environment.yml @@ -0,0 +1,11 @@ +# Binary deps (GDAL, pycolmap) with no reliable cross-platform wheels. +# `make install` layers the rest on top. See README.md for setup. +name: kamera +channels: + - conda-forge +dependencies: + - python=3.13 # must match PYTHON_VERSION in the Makefile + - gdal>=3.10 + - pycolmap>=4.2 + - pip + - uv diff --git a/kamera/calibration/README.md b/kamera/calibration/README.md new file mode 100644 index 00000000..30379457 --- /dev/null +++ b/kamera/calibration/README.md @@ -0,0 +1,57 @@ +# Rig Calibration + +Calibrates every camera on a KAMERA rig from one calibration flight (figure eights at several altitudes) and expresses them in the INS body frame. One COLMAP model holds all modalities: the trigger-synchronized images of each event form a *frame* with a single rig pose, so IR ties into the EO model through the rig without any cross-modal matching. + +```bash +conda activate kamera +kamera-calibrate /data/052025_Calibration # everything +kamera-calibrate /data/052025_Calibration --max_frames 150 --frame_stride 3 # quick look +kamera-calibrate --help +``` + +For a walkthrough of every stage, see [how_it_works.md](how_it_works.md). + +## Stages + +Each stage resumes from `/calibration/`, skipping whatever already exists. + +1. **frames** — `*_meta.json` grouped by trigger time into frames; camera names are `_` (`C_rgb`, `L_ir`, ...). IR and UV are contrast stretched to 8 bit; RGB is symlinked. +2. **features** — SIFT per camera with an initial focal length and distortion per modality, then an INS position prior per image (`InsTrajectory` interpolates the meta.json samples). +3. **match** — spatial matching from the priors, across all cameras, so figure-eight crossovers are matched as well as neighbours in time. Thermal-to-visible pairs are dropped: SIFT cannot match them and their few spurious inliers mislead the mapper. +4. **pass1** — incremental mapping with independent cameras and position priors. EO and IR come out as separate models, both in INS ENU. Needs three-view overlap along track: at 64 m/s and 1 frame/s that means flying above roughly 600 m AGL for these lenses; lower legs only register through crossovers with higher passes. +5. **pass2** — `cam_from_rig` for every camera is averaged from pass 1 (frames shared with the reference camera, both models being in INS ENU), the rig and frames are written to the database and onto the largest pass-1 model, and the images pass 1 never posed (IR) are added to their frames. Every image is then triangulated from the rig poses and bundle adjusted against the INS position priors, twice: rig poses and `sensor_from_rig` first, then with the intrinsics free as well. +6. **calibrate** — INS boresight (`ins_from_rig`) and lever arm as a robust average over frames, per-camera models, and `rig.yaml`. +7. **registration** — per channel `ir->rgb` and `uv->rgb` homographies as DIVE camera-registration JSON (format v2), plus flip GIFs. +8. **report** — PDF with the flight summary, intrinsics, rig geometry and registration overlays. + +## Outputs + +All output is directed to `/calibration/camera_models/`: + +- `_.yaml` — `standard` camera model readable by `kamera.colmap_processing.camera_models.load_from_file`, with the rig and calibration provenance in extra keys. +- `_rig.yaml` — `cam_from_rig` per camera, `ins_from_rig`, lever arm, quality statistics. +- `dive_registration/_to__registration.json`, `gifs/`, `_calibration_report.pdf`. + +## Exposure Timing + +The cameras do not expose at the same instant after the shared trigger; see [exposure_timing.md](exposure_timing.md) for the measurements, the manuals, and what the pipeline does about it. + +## Error Sources + +What limits the accuracy of the result, and how each source shows up in the outputs. + +**INS attitude at the trigger.** Each meta.json carries one INS sample taken shortly before the trigger, so the attitude used for a frame can be up to 10 ms old. In a figure-eight turn at about 5 degrees per second that is up to 0.05 degrees, roughly 25 RGB pixels on the ground, and it enters every frame's boresight estimate as noise. An event-stamped INS sample or a full-rate INS log would remove it; `InsTrajectory` accepts either without code changes. + +**Model drift.** The INS position priors pin the model's scale, heading and position, but its orientation still drifts slowly along the flight, and that drift is what dominates the per-frame boresight scatter reported in the rig yaml. The rig constraint removes any freedom between cameras within a frame, so the relative camera geometry, and therefore the homographies, is far better determined than the absolute boresight. + +**Exposure timing.** A camera that exposes later than the reference camera sees the ground further along track, by ground speed times the delay. A bundle adjustment on a moving rig cannot tell that from a camera mounted that far forward, so the delay shows up as an along-track lever arm. The rig table in the report reads that lever arm back into a time difference at the flight's ground speed, positive when the camera exposes after the reference. Only the relative timing between cameras is observable, since a delay shared by the whole rig is absorbed by the position priors. The camera yaml positions carry these offsets, which is correct at similar ground speeds. + +**Lever arms.** Beyond that timing signal the lever arms are weakly determined: at 400 to 900 m range a 30 cm baseline subtends less than one IR pixel. Read the reported translations with that in mind. The INS lever arm is the median offset of the rig origin from the INS position over all frames. + +**Homographies.** A homography maps one camera onto another exactly only for flat ground at one range, and the timing baseline above makes the range matter. Each pair is fit for the range in its page title (the survey altitude if given, otherwise the calibration flight's median scene range). The fit residual, rms and 95th percentile in RGB pixels, measures the lens distortion a single matrix cannot carry, and the overlay shows it visually as coloured fringes. + +## Conventions + +- `camera_quaternion` (x, y, z, w) rotates camera vectors into the INS body frame (forward, right, down); `camera_position` is in that frame, in metres. +- COLMAP's `cam_from_rig` maps rig (= reference camera) coordinates into the camera. +- The INS body-to-ENU rotation is `NED_TO_ENU * R_z(heading) R_y(pitch) R_x(roll)`, identical to `kamera.sensor_models.nav_state`. diff --git a/kamera/calibration/__init__.py b/kamera/calibration/__init__.py new file mode 100644 index 00000000..7bebaacc --- /dev/null +++ b/kamera/calibration/__init__.py @@ -0,0 +1,5 @@ +"""Multi-sensor rig calibration from a KAMERA calibration flight. + +Pipeline: synchronized frames -> COLMAP SfM with INS position priors -> rig bundle +adjustment -> camera models, rig geometry, INS boresight, DIVE homographies, PDF report. +""" diff --git a/kamera/calibration/cli.py b/kamera/calibration/cli.py new file mode 100644 index 00000000..51deef9d --- /dev/null +++ b/kamera/calibration/cli.py @@ -0,0 +1,269 @@ +"""``kamera-calibrate``: run the calibration pipeline stage by stage, resumable.""" + +from __future__ import annotations + +import json +import os +import shutil +import sys +from collections import Counter + +import cv2 +import numpy as np +import pycolmap as pc +from rich import print + +from kamera.calibration import registration, rig, sfm +from kamera.calibration.config import CalibrateConfig +from kamera.calibration.flight import build_image_tree, discover_flight +from kamera.calibration.report import FlightSummary, write_report +from kamera.colmap_processing.camera_models import StandardCamera + +# Homography pairs per channel, left -> right (DIVE registers the left onto the right). +# Only the pairs DIVE uses; ir->uv follows from the other two and only adds noise. +PAIRS = [("ir", "rgb"), ("uv", "rgb")] + +# A rig seed is trusted only when the per-frame estimates behind it agree. The rig +# bundle adjustment drops tracks over 4 px of reprojection error (about 0.13 deg for +# the IR cameras), so a seed a degree off stalls near the seed instead of converging. +SEED_MAX_SCATTER_DEG = 0.5 +SEED_MIN_CLUSTER_FRACTION = 0.5 + + +def write_gifs(frames, names, image_dir, left, right, h, gif_dir, count) -> dict: + """Flip GIFs of the left image warped onto the right, for evenly spaced frames. + + Returns the report overlay from the middle frame, or an empty dict when no frame + has both images or ``count`` is 0. + """ + os.makedirs(gif_dir, exist_ok=True) + # Both sides come from the normalized tree: the raw UV frames are nearly black. + by_time = {(c, t): n for n, (c, t) in names.items() if c in (left, right)} + usable = [f for f in frames if left in f.images and right in f.images] + out = {} + chosen = usable[:: max(1, len(usable) // max(count, 1))][:count] + for k, frame in enumerate(chosen): + left_img, right_img = ( + cv2.imread( + os.path.join(image_dir, by_time[(camera, frame.time)]), + cv2.IMREAD_COLOR, + ) + for camera in (left, right) + ) + warped, ref, mask = registration.warp_pair(left_img, right_img, h) + # Flip between the right image and the same with the warped left pasted over + # its footprint, the way DIVE shows a registration. + registration.write_gif( + os.path.join(gif_dir, f"{left}_to_{right}_{k}.gif"), + ref, + registration.composite(warped, ref, mask), + ) + if k == len(chosen) // 2: + out = {"overlay_img": registration.blend_overlay(warped, ref, mask)} + return out + + +def main(argv=None) -> None: + cfg = CalibrateConfig.cli(argv=argv, strict=True) + work = cfg.work_dir or os.path.join(cfg.flight_dir, "calibration") + image_dir, db_path = os.path.join(work, "images"), os.path.join(work, "database.db") + pass1_dir, rig_dir, camera_model_dir = ( + os.path.join(work, "pass1"), + os.path.join(work, "rig"), + os.path.join(work, "camera_models"), + ) + os.makedirs(work, exist_ok=True) + + def done(path: str) -> bool: + return os.path.exists(path) and not cfg.force + + def staging(path: str) -> str: + """A clean scratch path for a stage. Stages build there and ``publish`` moves + the result into place, so ``path`` only ever exists once its stage finished + and an interrupted run redoes the stage instead of skipping it.""" + tmp = path + ".partial" + for stale in (tmp, tmp + "-wal", tmp + "-shm", tmp + "-journal"): + if os.path.isdir(stale): + shutil.rmtree(stale) + elif os.path.exists(stale): + os.remove(stale) + return tmp + + def publish(tmp: str, path: str) -> None: + if os.path.isdir(path): + shutil.rmtree(path) + os.replace(tmp, path) + + print("[blue]Discovering frames[/blue]") + frames, ins, rig_name = discover_flight(cfg.flight_dir) + rig_name = cfg.rig_name or rig_name.replace("images_", "") or "rig" + discovered = frames + all_cameras = {camera for frame in frames for camera in frame.images} + full = [f for f in frames if len(f.images) == len(all_cameras)] + stop = ( + cfg.frame_start + cfg.max_frames * cfg.frame_stride if cfg.max_frames else None + ) + frames = full[cfg.frame_start : stop : cfg.frame_stride] + median_gap_ms = 1000 * np.median([ins.sample_gap(f.time) for f in frames]) + print( + f"{len(full)} frames with every camera, using {len(frames)}; " + f"INS median sample gap {median_gap_ms:.1f} ms" + ) + names = build_image_tree(frames, image_dir) + with open(os.path.join(work, "images.json"), "w") as f: + json.dump(names, f) + + if not done(db_path): + print("[blue]Extracting features and writing INS priors[/blue]") + tmp = staging(db_path) + sfm.extract_features( + tmp, + image_dir, + names, + cfg.focal_px, + cfg.distortion, + cfg.max_image_size, + cfg.num_features, + ) + sfm.write_pose_priors(tmp, names, ins, cfg.prior_std_m) + print("[blue]Matching[/blue]") + sfm.match_features(tmp, cfg.match_distance_m, cfg.match_neighbors) + publish(tmp, db_path) + + if not done(pass1_dir): + print("[blue]Pass 1: mapping with independent cameras[/blue]") + tmp = staging(pass1_dir) + sfm.run_mapping(db_path, image_dir, tmp) + publish(tmp, pass1_dir) + pass1 = sfm.load_models(pass1_dir) + for k, r in pass1.items(): + print( + f" model {k}: {r.num_reg_images()} images, {r.num_points3D()} points, " + f"{r.compute_mean_reprojection_error():.2f} px" + ) + + if not done(rig_dir): + print("[blue]Pass 2: rig bundle adjustment[/blue]") + for name, v in sfm.derive_rig(pass1, names, cfg.reference_camera).items(): + fraction = v["frames"] / v["frames_total"] + print( + f" {name}: {v['frames']}/{v['frames_total']} frames in cluster, " + f"rotation scatter {v['rotation_scatter_deg']:.3f} deg, " + f"translation std {np.round(v['translation_std_m'], 2)} m" + ) + if ( + v["rotation_scatter_deg"] > SEED_MAX_SCATTER_DEG + or fraction < SEED_MIN_CLUSTER_FRACTION + ): + print( + f" [yellow]{name}: rig seed is unreliable (scatter over " + f"{SEED_MAX_SCATTER_DEG} deg or under " + f"{SEED_MIN_CLUSTER_FRACTION:.0%} of frames in the cluster). " + "Pass 2 may stall near this seed: check its observation count " + "below and its registration GIFs.[/yellow]" + ) + rig_in = os.path.join(work, "rig_init") + sfm.rigged_model(db_path, pass1, names, cfg.reference_camera, rig_in) + tmp = staging(rig_dir) + sfm.refine_rig(db_path, names, rig_in, tmp) + publish(tmp, rig_dir) + model = pc.Reconstruction(rig_dir) + print( + f" rig model: {model.num_reg_frames()} frames, " + f"{model.num_reg_images()} images, " + f"{model.compute_mean_reprojection_error():.2f} px" + ) + + print("[blue]Extracting camera models and boresight[/blue]") + cal = rig.calibrate_rig( + model, + names, + ins, + cfg.reference_camera, + rig_name, + os.path.basename(os.path.abspath(cfg.flight_dir)), + ) + for name in sorted(cal.cameras): + c = cal.cameras[name] + print( + f" {name}: {c.frames} frames, {c.observations} observations, " + f"{c.reproj_rms_px:.2f} px rms" + ) + for p in rig.write_outputs(cal, camera_model_dir): + print(f" wrote {p}") + # /_view/: postflight reads /sys_config.json. + config_dirs = { + os.path.dirname(os.path.dirname(p)) for f in frames for p in f.images.values() + } + for p in rig.write_sys_configs( + cal, camera_model_dir, sorted(config_dirs), cfg.install_sys_config + ): + print(f" wrote {p}") + + print("[blue]Fitting homographies and writing DIVE registration files[/blue]") + reg_dir = os.path.join(camera_model_dir, "dive_registration") + cams = { + n: StandardCamera( + c.width, + c.height, + c.K, + c.dist, + cal.camera_position(n), + cal.camera_quaternion(n), + ) + for n, c in cal.cameras.items() + } + range_m = cfg.registration_range_m or cal.scene_range_m + registered = {names[im.name][1] for im in model.images.values() if im.has_pose} + gif_frames = [f for f in frames if f.time in registered] + gif_dir = os.path.join(camera_model_dir, "gifs") + print( + f" homographies exact at {range_m:.0f} m range; " + f"ground speed {cal.ground_speed_mps:.0f} m/s" + ) + pairs = [] + for channel in sorted({n.split("_")[0] for n in cams}): + for left_mod, right_mod in PAIRS: + left, right = f"{channel}_{left_mod}", f"{channel}_{right_mod}" + if left not in cams or right not in cams: + continue + try: + h, stats = registration.model_homography( + cams[left], cams[right], range_m + ) + except ValueError as e: + print(f" [yellow]{left} -> {right}: {e}[/yellow]") + continue + source = registration.source_stamp(cfg.flight_dir, {"rig": rig_name}) + path = registration.write_dive_registration( + reg_dir, left, right, h, stats, source + ) + print(f" wrote {path} (fit rms {stats['rmsPx']:.2f} px)") + images = write_gifs( + gif_frames, names, image_dir, left, right, h, gif_dir, cfg.gif_frames + ) + pairs.append( + {"left": left, "right": right, "h": h, "stats": stats, **images} + ) + + last = cfg.frame_start + (len(frames) - 1) * cfg.frame_stride + summary = FlightSummary( + discovered=len(discovered), + complete=len(full), + selected=frames, + selection=( + "all complete frames" + if len(frames) == len(full) + else f"frames {cfg.frame_start} to {last} of {len(full)}, " + f"stride {cfg.frame_stride}" + ), + images_on_disk=Counter(c for f in discovered for c in f.images), + ins=ins, + ) + report_path = os.path.join(camera_model_dir, f"{rig_name}_calibration_report.pdf") + write_report(report_path, cal, pairs, summary) + print(f"[green]Report written to {report_path}[/green]") + + +if __name__ == "__main__": + main(sys.argv[1:]) diff --git a/kamera/calibration/config.py b/kamera/calibration/config.py new file mode 100644 index 00000000..79baf8e0 --- /dev/null +++ b/kamera/calibration/config.py @@ -0,0 +1,69 @@ +"""Command-line configuration for the rig calibration.""" + +from __future__ import annotations + +import scriptconfig as scfg + + +class CalibrateConfig(scfg.DataConfig): + __command__ = "kamera-calibrate" + flight_dir = scfg.Value( + None, + position=1, + help="KAMERA flight directory (contains /_view/*_meta.json)", + ) + work_dir = scfg.Value( + None, help="Scratch and output root (default: /calibration)" + ) + rig_name = scfg.Value( + None, + help="Name used in output file names (default: sys_cfg from the meta json)", + ) + reference_camera = scfg.Value( + "C_rgb", + help="Rig reference sensor; every other camera is expressed relative to it", + ) + frame_start = scfg.Value(0, help="Index of the first synchronized frame to use") + frame_stride = scfg.Value(1, help="Use every Nth frame") + max_frames = scfg.Value(0, help="Cap on frames used (0 = all)") + focal_px = scfg.Value( + {"rgb": 31363.0, "uv": 14120.0, "ir": 1712.0}, + help="Initial focal length per modality in pixels; refined by SfM", + ) + distortion = scfg.Value( + {"rgb": [0.065, -0.13], "uv": [-0.22, 0.0], "ir": [-0.24, -0.4]}, + help="Initial OpenCV k1, k2 per modality. Held fixed while the pass 1 model " + "grows (flat ground cannot pin distortion down from a few views) and refined " + "with the whole rig in pass 2", + ) + max_image_size = scfg.Value( + 3200, help="Images are downsampled to this longest side for SIFT" + ) + num_features = scfg.Value(8192, help="Max SIFT features per image") + match_distance_m = scfg.Value( + 250.0, help="Spatial matching radius from INS positions" + ) + match_neighbors = scfg.Value( + 90, + help="Spatial matching neighbours per image " + "(about 10 frames times the number of cameras)", + ) + prior_std_m = scfg.Value( + 2.0, help="Standard deviation assigned to INS position priors" + ) + registration_range_m = scfg.Value( + 0.0, + help="Ground range the homographies are exact at; 0 = median scene range of " + "the calibration model. Set to the survey AGL", + ) + install_sys_config = scfg.Value( + False, + isflag=True, + help="Point the flight's sys_config.json at the calibrated camera models, so " + "postflight (flight summary, footprint KMLs) uses them; the original is kept " + "as sys_config.json.orig. A copy is always written beside the models", + ) + gif_frames = scfg.Value(5, help="Registration GIFs written per camera pair") + force = scfg.Value( + False, isflag=True, help="Rerun stages whose outputs already exist" + ) diff --git a/kamera/calibration/exposure_timing.md b/kamera/calibration/exposure_timing.md new file mode 100644 index 00000000..2c7ab1ba --- /dev/null +++ b/kamera/calibration/exposure_timing.md @@ -0,0 +1,128 @@ +# Camera exposure timing on the KAMERA rig + +Written 2026-09-16 from the May 2025 calibration flight (`052025_Calibration`). + +## The short version + +All nine cameras get the same trigger pulse, but they do not take their pictures at +the same moment. Each camera type has its own delay between the trigger and the middle +of its exposure: + +| camera | when the middle of the exposure happens | how we know | +|---|---|---| +| RGB (Phase One iXM-GS120, electronic shutter) | about 20 ms after the trigger | Phase One guide | +| UV (Prosilica GT4907) | about 10 ms after the trigger (half of a 20 ms exposure) | Prosilica manual + measurement | +| IR (FLIR A6750) | within a few ms of the trigger | FLIR manual + measurement | + +On top of that, the INS reading saved with each image is the last 100 Hz sample +*before* the trigger, so it is on average about 8 to 10 ms old. + +At 65 m/s, 20 ms is 1.3 m on the ground. So the RGB, UV and IR pictures of one +"frame" were taken from three slightly different places along the flight line, and +the INS reading belongs to a fourth. + +For now the calibration handles this in software (see "What we do about it now"). +The right long-term fix is in hardware (see "What we should do later"). + +## How we found it + +1. **The GIFs looked wrong.** The IR-to-RGB registration GIFs showed the IR image + sitting a metre or two off from the RGB image, even though the calibration itself + reported sub-pixel fits. + +2. **We measured the offset instead of eyeballing it.** For each GIF we lined up the + edges of the warped IR frame with the RGB frame (phase correlation on gradient + images) and converted the shift to metres using the INS altitude. Result: the shift + was about 1.5 m, almost entirely along the direction of flight, and it was the + *same in metres* at 400 m and at 900 m altitude. A camera pointing error would grow + with altitude. A constant distance along the flight line is what a time delay looks + like. + +3. **The 3D model said the same thing.** Bundle adjustment had placed all three IR + camera centres about 1.0 m behind the RGB camera along the flight line, and the UV + centres about 0.6 m behind, on a rig whose real spacing is a few tens of + centimetres. A rig flying in a straight line cannot tell "this camera exposed 15 ms + earlier" from "this camera is mounted 1 m further back", so the adjustment turned + the timing into a fake lever arm. We confirmed the direction with nothing but the + model's camera positions and the INS velocity: IR behind RGB by 0.93 to 1.01 m, UV + behind RGB by 0.60 to 0.67 m, consistent over all 740 frames. + +4. **We checked whether the RGB is "on time".** It cannot be told from the model: + the INS position priors define where the model sits, so a delay shared by every + camera is invisible. Only the differences between cameras are measurable. Trying to + read the shared delay off the turns (a delay shows up as a pointing error that + scales with turn rate) gave a weak +16 ms with most of the residual unexplained, + so the manuals had to settle the absolute numbers. + +5. **We checked how long the IR actually exposes.** A 30 ms integration at 65 m/s + would smear the IR picture by 2 m, which is 8 pixels at 400 m altitude, and would + visibly blur edges along the flight line. The raw IR frames have the same sharpness + along and across track, so the integration in use is a few milliseconds at most. + +6. **We read the manuals** (next section) and the numbers lined up. + +## Supporting documentation + +- **Phase One, iXM-GS120 Operation Guide, Rev 1.0.0, section 3.1 "Exposure Sequence".** + The table "Hardware Pulses and Delay Parameter Signals" gives Trigger IN to Mid + Exposure as "~20 msec + 0.5 x Exposure Time" for the electronic shutter and + "~25 msec + 0.5 x Exposure Time" for the leaf shutter. The camera was run with the + electronic shutter and a 0.3 ms exposure (from the image metadata), so its + mid-exposure is about 20 ms after the trigger. The same table shows the camera + outputs a **Mid-Exposure Pulse** on its own signal line. + +- **Teledyne FLIR, A6000 and A8500 Series User's Manual, section 5.4.2 "Frame Sync + Starts".** There is no single "latency" number. The camera has two sync modes: Frame + Sync Starts Integration ("take a picture now") and Frame Sync Starts Readout (the + sync reads out the previous frame and the exposure is placed automatically). Either + way the exposure sits within a few ms of the sync edge, plus or minus half the + integration time. Section 6.5.5 describes the Sync In as a rising-edge TTL signal + with no stated delay. The KAMERA driver (`genicam_a6750.launch`) sets the frame sync + source to External and does not set the sync mode or the integration time, so both + come from the preset stored in the camera. Note: the older "A6xx series" manual + covers the uncooled A615/A655 (640 x 480) and does not apply to the A6750 + (640 x 512). + +- **Allied Vision, Prosilica GT Technical Manual V3.3.3, "Trigger timing concept" + (camera interfaces chapter).** Trigger latency is defined as the delay from the user + trigger to the start of exposure; the sibling models list 0.7 to 25.8 microseconds. + So the UV exposure starts at the trigger for all practical purposes and its middle + is half the exposure later. The GT4907 itself was removed from this manual in + V3.2.1 as a discontinued model, so it has no spec table there. KAMERA runs the UV + with auto-exposure (driver default, capped at 100 ms); the measured 10 ms offset + matches a 20 ms exposure, which is also the value written in the comments of + `prosilica.launch`. + +Putting the three together: RGB middle at +20 ms, UV middle at +10 ms, IR middle +near 0 ms. The model measured IR 15 ms before RGB and UV 10 ms before RGB. That agrees +to within the "~" in the Phase One table. + +## What we do about it now + +- The rig calibration keeps the timing as along-track offsets in the camera + positions. `rig.yaml` reports each camera's forward offset and the exposure time + difference it implies (`exposure_offset_from_reference_ms`, negative = earlier than + the RGB). The camera yaml positions carry the same offsets, which is correct as long + as the survey flies at a similar ground speed. +- The DIVE homographies are fit for a nominal ground range + (`--registration_range_m`, default: the calibration flight's median scene range) + instead of at infinity, because a homography at infinity throws the offset away. + Set it to the survey altitude above ground for the best registration there. +- `InsTrajectory` interpolates between INS samples, so a denser INS log removes the + 8 to 10 ms staleness without code changes. + +## What we should do later + +1. Log the full-rate INS stream (or post-process with POSPac) so the navigation + solution can be interpolated to any timestamp. +2. Feed the Phase One mid-exposure pulse into an INS event input. That timestamps + the RGB exposure directly and removes the "~20 ms". +3. Trigger the IR from that same pulse; its exposure then starts at the RGB + mid-exposure and ends a few ms later. +4. Fix the UV exposure (no auto) and delay its trigger by 20 ms minus half the + exposure, using the GT trigger-delay feature, so its middle lands on the pulse too. + At minimum, record the UV exposure in the meta json. + +With all three mid-exposures on one timestamped pulse, the "one pose per frame" +assumption in the rig model becomes exactly true, the rig offsets become physical, +and the homographies stop depending on speed and altitude. diff --git a/kamera/calibration/flight.py b/kamera/calibration/flight.py new file mode 100644 index 00000000..1214e033 --- /dev/null +++ b/kamera/calibration/flight.py @@ -0,0 +1,175 @@ +"""Flight discovery: synchronized frames, camera names, INS trajectory, image tree.""" + +from __future__ import annotations + +import bisect +import glob +import json +import os +from concurrent.futures import ProcessPoolExecutor +from dataclasses import dataclass, field + +import cv2 +import numpy as np +from scipy.spatial.transform import Rotation, Slerp + +from kamera.sensor_models.nav_conversions import llh_to_enu + +# Image suffixes written by the KAMERA archiver, keyed by modality. +MODALITY_EXT = {"rgb": ".jpg", "uv": ".jpg", "ir": ".tif"} +# NED body attitude -> ENU: swap north/east and flip down (180 deg turn about (1,1,0)). +NED_TO_ENU = Rotation.from_quat([np.sqrt(0.5), np.sqrt(0.5), 0.0, 0.0]) + + +@dataclass +class Frame: + """All images captured on one trigger event, keyed by camera name (``C_rgb``).""" + + time: float + images: dict[str, str] = field(default_factory=dict) + + +class InsTrajectory: + """INS attitude and ENU position interpolated to any time. + + Quaternions follow the KAMERA convention: ``rotation`` maps body (forward, right, + down) vectors into the local ENU frame. Any source with times, lat/lon/alt and + heading/pitch/roll can build one, so a future high-rate or event-stamped log drops + in via ``__init__``. + """ + + def __init__(self, times, llh_deg, hpr_deg, lat0=None, lon0=None, h0=0.0): + order = np.argsort(times) + self.times = np.asarray(times, float)[order] + self.llh = np.asarray(llh_deg, float)[order] + hpr = np.radians(np.asarray(hpr_deg, float)[order]) + self.lat0 = float(np.median(self.llh[:, 0]) if lat0 is None else lat0) + self.lon0 = float(np.median(self.llh[:, 1]) if lon0 is None else lon0) + self.h0 = float(h0) + self.enu = np.array( + [ + llh_to_enu(*r, self.lat0, self.lon0, self.h0, in_degrees=True) + for r in self.llh + ] + ) + self.rotations = NED_TO_ENU * Rotation.from_euler("ZYX", hpr) + + @classmethod + def from_meta(cls, samples: dict[float, tuple]) -> InsTrajectory: + rows = np.array([samples[t] for t in sorted(samples)]) + return cls(sorted(samples), rows[:, :3], rows[:, 3:]) + + def _segment(self, t: float) -> int: + """Index ``i`` with ``t`` between samples ``i - 1`` and ``i`` (clamped).""" + return int(np.clip(bisect.bisect(self.times, t), 1, len(self.times) - 1)) + + def pose(self, t: float) -> tuple[np.ndarray, Rotation]: + i = self._segment(t) + w = float( + np.clip( + (t - self.times[i - 1]) / (self.times[i] - self.times[i - 1]), 0.0, 1.0 + ) + ) + pos = (1 - w) * self.enu[i - 1] + w * self.enu[i] + rot = Slerp([0.0, 1.0], self.rotations[[i - 1, i]])([w])[0] + return pos, rot + + def sample_gap(self, t: float) -> float: + """Seconds from ``t`` to the nearest INS sample (how stale the attitude is).""" + i = self._segment(t) + return float(min(abs(t - self.times[i - 1]), abs(t - self.times[i]))) + + # Duck-type the NavStateProvider interface used by camera_models. + def pos(self, t): + return self.pose(t)[0] + + def quat(self, t): + return self.pose(t)[1].as_quat() + + +def discover_flight(flight_dir: str) -> tuple[list[Frame], InsTrajectory, str]: + """Group every ``*_meta.json`` under ``flight_dir`` into synchronized frames. + + Camera names are ``_`` with the channel taken from the view + directory (``center_view`` -> ``C``). Returns frames sorted by time, the INS + trajectory assembled from the per-image INS samples, and the rig name from + ``sys_cfg``. + """ + frames: dict[float, Frame] = {} + samples: dict[float, tuple] = {} + rig_name = "" + for meta in glob.glob( + os.path.join(flight_dir, "**", "*_view", "*_meta.json"), recursive=True + ): + with open(meta) as f: + d = json.load(f) + view_dir = os.path.basename(os.path.dirname(meta)) # e.g. center_view + channel = view_dir[0].upper() + stem = meta[: -len("_meta.json")] + t = float(d["evt"]["time"]) + frame = frames.setdefault(round(t, 3), Frame(time=t)) + for modality, ext in MODALITY_EXT.items(): + if os.path.exists(stem + f"_{modality}{ext}"): + frame.images[f"{channel}_{modality}"] = stem + f"_{modality}{ext}" + ins = d["ins"] + samples[float(ins["time"])] = ( + ins["latitude"], + ins["longitude"], + ins["altitude"], + ins["heading"], + ins["pitch"], + ins["roll"], + ) + rig_name = rig_name or d.get("sys_cfg", "") + if not frames: + raise FileNotFoundError(f"No *_meta.json files found under {flight_dir}") + return ( + [frames[k] for k in sorted(frames)], + InsTrajectory.from_meta(samples), + rig_name, + ) + + +def normalize(src: str, dst: str) -> None: + """Percentile-stretch (0.1-99.9) a dim or 16-bit frame to 8 bits and apply CLAHE. + + Gives SIFT some contrast to work with on UV and IR. + """ + im = cv2.imread(src, cv2.IMREAD_UNCHANGED).astype(np.float32) + lo, hi = np.percentile(im, [0.1, 99.9]) + im = np.clip((im - lo) / max(hi - lo, 1.0) * 255.0, 0, 255).astype(np.uint8) + cv2.imwrite( + dst, + cv2.createCLAHE(clipLimit=1.0, tileGridSize=(5, 5)).apply(im), + [cv2.IMWRITE_JPEG_QUALITY, 95], + ) + + +def build_image_tree( + frames: list[Frame], image_dir: str +) -> dict[str, tuple[str, float]]: + """Lay frames out as ``image_dir//.jpg`` for COLMAP. + + COLMAP assigns one camera per folder and groups images into rig frames by identical + file names across folders, hence the time-based names. RGB is symlinked; the dim UV + and 16-bit IR frames are contrast-normalized. + Returns ``{colmap image name: (camera name, frame time)}``. + """ + names: dict[str, tuple[str, float]] = {} + to_normalize: list[tuple[str, str]] = [] + for frame in frames: + for camera, src in frame.images.items(): + name = f"{camera}/{frame.time:.3f}.jpg" + dst = os.path.join(image_dir, name) + os.makedirs(os.path.dirname(dst), exist_ok=True) + names[name] = (camera, frame.time) + if os.path.exists(dst): + continue + if camera.endswith("_rgb"): + os.symlink(os.path.abspath(src), dst) + else: + to_normalize.append((src, dst)) + if to_normalize: + with ProcessPoolExecutor() as pool: + list(pool.map(normalize, *zip(*to_normalize))) + return names diff --git a/kamera/calibration/how_it_works.md b/kamera/calibration/how_it_works.md new file mode 100644 index 00000000..2b79579e --- /dev/null +++ b/kamera/calibration/how_it_works.md @@ -0,0 +1,165 @@ +# Rig Calibration + +This follows one run of `kamera-calibrate ` from the raw KAMERA flight folder to the camera models. File names in `kamera/calibration/` +are given so you can read along in the code. + +## Inputs + +A KAMERA flight folder, for example `052025_Calibration/`, with one folder per view +(`center_view`, `left_view`, `right_view`) and, for every trigger, four files with a +common stem: + +``` +taiga_calibration_2025_fl118_C_20250503_203245.017993_meta.json +taiga_calibration_2025_fl118_C_20250503_203245.017993_rgb.jpg +taiga_calibration_2025_fl118_C_20250503_203245.017993_uv.jpg +taiga_calibration_2025_fl118_C_20250503_203245.017993_ir.tif +``` + +The meta json holds the trigger time (`evt.time`), the INS reading nearest to it +(`ins`: latitude, longitude, altitude, heading, pitch, roll), and the camera metadata. + +## Outputs + +All output is directed to `/calibration/camera_models/`: + +- one yaml file per camera (`_.yaml`) +- `_rig.yaml` with the rig geometry and the INS boresight +- `dive_registration/*.json`, one homography file per camera pair per channel (usable in DIVE / VIAME) +- `gifs/`, flip animations of one camera warped onto another +- `_calibration_report.pdf`, a summary of the camera models, their positions, and a +single overlay for each camera pair, with EO as the reference. + +Everything else under `/calibration/` is intermediate and can be deleted +as the tool rebuilds whatever is missing and skips whatever exists. + +## Summary + +Structure from motion (COLMAP) can work out where every picture was taken from and what it was looking at, just from the pictures overlapping each other and using structure from motion (SfM). It does this in its own arbitrary coordinate system, so we hand it the INS positions to pin the model to the real world. Because all nine cameras fire on the same trigger, the nine pictures of one trigger share one rig position and orientation, so COLMAP (3.12+) can enforce that ("rigs" and "frames"). + +Once the model is solved with that constraint, the fixed rotation and offset of each camera relative to the reference camera is obtained, and comparing the rig orientation with the INS orientation over hundreds of frames gives the boresight. The boresight is the relative position and angle of the whole camera mount to the INS. The only thing COLMAP cannot do for us is match thermal pictures to visible ones due to limitations with SIFT features, but with the rig constraint it does not need to, since thermal images can match intra-modal with SIFT, just not inter-modal. + +## Step 1: Find Imagery (`flight.py`, `discover_flight`) + +Read every `*_meta.json`. Group them by trigger time, rounded to a millisecond, so the +L, C and R files of one trigger become one **frame**. Name each image +`_`, for example `C_rgb` or `L_ir`. Collect the INS readings from +every json into one time-ordered trajectory. Frames missing any of the nine images are +dropped, so every frame used has all nine. + +The INS trajectory (`InsTrajectory`) converts latitude/longitude/altitude to metres in a local east-north-up (ENU) frame centred on the flight, and heading/pitch/roll into a rotation, using the same convention as the rest of KAMERA (`sensor_models.nav_state`). Asked for the pose at any time, it interpolates between the two nearest samples. + +## Step 2: Organize Imagery (`flight.py`, `build_image_tree`) + +COLMAP wants one folder per camera and, for rigs, the *same file name* across folders +for pictures of the same frame. So the tree is +`calibration/images//.jpg`. RGB files are symlinks. UV and IR are rewritten: the UV frames are very dark and the IR frames are 16-bit, so both are stretched between their 0.1 and 99.9 percentiles and given a mild local contrast boost (CLAHE). This runs in parallel because there are generally thousands of files. + +## Step 3: Extract Features and Geotag (`sfm.py`, `extract_features`, `write_pose_priors`) + +SIFT features are extracted per camera folder on the GPU, with the image downsampled +to 3200 px on the long side (a 12768 px RGB frame gives about 12,000 features). Each +camera folder gets one COLMAP camera with the OPENCV model (focal length, principal +point, k1, k2, p1, p2), seeded with a rough focal length and k1, k2 per modality from +the config so the first frames register cleanly. + +Then every image gets a **position prior**: the INS position (lat,lon,alt) at its trigger time, with a 2 m standard deviation. COLMAP uses these priors in two ways later: to decide which images to try to match, and to keep the model in real-world metres and orientation. + +## Step 4: Feature Matching (`sfm.py`, `match_features`, `prune_cross_spectral`) + +Rather than matching every image against every other (8,800 images would be 39 million pairs) that exhaustive matching would require, each image is matched against its 90 nearest neighbours by INS position within 250 m. That covers the frames just before and after, and also the crossovers of the figure eights, which are what make the geometry strong. + +The neighbours include every camera, so the same-frame RGB and UV pictures get matched too, which is useful: they share features and tie the UV into the RGB model directly. Thermal-to-visible pairs also get "matched" occasionally, but those matches are mostly noise (about 25 random inliers), and left in they pull IR images to wrong places. They are deleted from the database right after matching. + +## Step 5: Incremental Mapping (`sfm.py`, `run_mapping`) + +COLMAP's incremental mapper builds the 3D model: it picks a good starting pair, +triangulates points, adds the next image by matching its features to points already in +3D, and periodically re-optimises everything (bundle adjustment). The position priors +are switched on, so the model comes out in INS coordinates rather than an arbitrary +frame, with the right scale. + +At this stage every camera is still independent. The result is normally two models: +one with all the EO cameras (RGB and UV of L, C and R, linked by same-frame matches +and by the overlap strips between channels) and one with the IR cameras, which only +match each other. Both are in INS coordinates thanks to the priors, so they can be +compared. + +Two practical notes. The mapper needs a point to be seen from three pictures to add +a third picture; at 300 to 400 m above ground with one frame per second the along-track +overlap is under 50%, so those legs only register through crossovers with higher +passes. And the global bundle adjustment is set to run every 30% of growth instead of +10%, which halved the run time on the full flight (about 2.5 hours for 8,800 images). + +Lens distortion is held at its per-modality seed throughout pass 1, with only the focal length free. Two or three views of flat ground cannot pin distortion down. Pass 2 refines the full intrinsics once every camera is posed on the rig. + +## Step 6: Extract Rig (`sfm.py`, `derive_rig`, `robust_mean`) + +For every camera and every frame where both that camera and the reference camera +(`C_rgb`) were placed, compute the camera's pose relative to the reference. On a rigid rig that relative pose is the same every frame, so the hundreds of estimates should agree. Take the densest cluster of them (the estimate with the most neighbours within one degree, then the mean of that cluster) rather than a plain median, because a badly registered part of a model can put half the estimates 20 degrees off, and the cluster ignores those. For the IR cameras this comparison goes across the two models, it works because both models are in INS coordinates, and it is accurate to about 0.3 degrees, which is plenty for a starting seed. + +The script prints, per camera, how many of the shared frames fell in the cluster and how tightly they agree, and warns when the scatter is over half a degree or under half the frames made the cluster. The rig bundle adjustment only refines a seed it can triangulate from: the triangulator drops tracks over 4 px of reprojection error, about 0.13 degrees for IR, and a seed a degree off loses the crossover tracks that pin the offset, so the offset stalls near the seed rather than blowing up. A warning here means the IR result needs checking, not that the run failed. + +## Step 7: Rig Bundle Adjustment (`sfm.py`, `rigged_model`, `refine_rig`) + +Now we tell COLMAP about the rig with the following: + +1. Write the rig definition into the database: `C_rgb` is the reference sensor and + every other camera has the starting `cam_from_rig` from step 6. COLMAP groups the + images into frames by their shared file name. +2. Put the rig onto the largest pass 1 model. Its frames now hold one pose each, taken + from the `C_rgb` image. Add the images pass 1 never placed, mostly IR: they inherit + their pose from the frame pose and the rig offsets. +3. Triangulate every image again from those poses. IR features now become 3D points + too, because the IR images have poses even though nothing matched them to EO. +4. Bundle adjust with the INS position priors, refining the frame poses and the + `cam_from_rig` of every camera, with intrinsics held fixed. +5. Triangulate again from the refined poses, and bundle adjust once more with the + intrinsics free (focal length and distortion). + +The result is one model with all nine cameras. On the full May 2025 flight: 740 +frames, 6,660 images, 0.71 px mean reprojection error, matching a 250-frame subset +to about 0.05 degrees on the rig angles. + +Two things that did not work, so nobody repeats them: continuing COLMAP's incremental +mapper from the rigged model (it throws the model away as "insufficient size"), and +COLMAP's plain bundle adjuster on the rigged model (without a fixed gauge it diverges). +The triangulate-then-adjust route above is stable. + +## Step 8: Extract Calibrations (`rig.py`, `calibrate_rig`) + +From the final model: + +- **Intrinsics** per camera: focal lengths, principal point, distortion, straight from +COLMAP's OPENCV camera. The per-camera reprojection error is computed over every +observation of that camera, and the observation count is reported next to it. That +count is the check on a stalled IR seed: the reprojection error only covers tracks +that survived triangulation, so it stays small even when most IR tracks were dropped, +while the observation count collapses. +- **Rig geometry**: `cam_from_rig` for each camera, which maps rig coordinates (the +`C_rgb` camera frame) into that camera. This gives the relative static poses for each camera. +- **INS boresight**: for every frame, take the rig's orientation in the world from the model and the INS orientation at the same time, and compute the rotation between them. That should be one fixed rotation - the densest-cluster mean of it over all frames is `ins_from_rig`, and the spread of the individual frames about it is the grounded per-frame uncertainty. The same comparison of positions gives the lever arm from the INS to the rig, which is noise dominated. +- **Camera models in the INS frame**: each camera's rotation into the INS body is +`ins_from_rig` composed with the camera's rotation into the rig, and its position is the lever arm plus its rig center rotated into the body frame. These two numbers are the `camera_quaternion` and `camera_position` in the yaml, exactly as the existing +KAMERA georegistration code expects. + +`write_camera_yaml` and `write_rig_yaml` put all of this on disk, with the original +keys first so old readers still work and the provenance after. + +## Step 9: Registration Homographies for DIVE / VIAME(`registration.py`, `cli.py`) + +For each channel and each pair `ir->rgb`, `uv->rgb`: take a grid of pixels in the first camera, cast them out to a nominal ground range through the calibrated model, project them into the second camera, and fit one 3x3 homography to the result. The fit residual says how much a single matrix loses to lens distortion. The range matters because the cameras do not expose at exactly the same instant, which shows up as an along-track offset of about a meter in the rig (see `exposure_timing.md`); it +defaults to the calibration flight's median scene range and should be set to the +survey altitude. The files use DIVE's registration format version 2, one matrix-only +pair each. + +The GIFs show the registration the way DIVE does: for five frames spread across the flight, each one flips between the RGB frame and the same frame with the first camera warped onto it over its footprint, so a misregistration is visible at a glance. + +## Step 10: Report (`report.py`) + +A PDF with a flight summary page (dates, frames on disk, selected and registered, images per camera, the flight track), the camera intrinsics table, the rig geometry with a sketch of the optical axes in aircraft body axes, and one page per homography pair showing the RGB frame with the warped camera blended over its footprint, as DIVE displays a registration. The boresight numbers and their per-frame scatter are in the rig yaml, and the README describes what limits their accuracy. + +## Running it again + +Every stage checks for its outputs and skips itself when they exist, so rerunning the same command after a crash or a code change in a late stage is much shorter. Delete `calibration/pass1` to redo the mapping, `calibration/rig` to redo the rig +adjustment, or pass `--force` to redo everything. `--max_frames` and `--frame_start` select a subset for quick experiments, try to pick frames from the high-altitude part of the flight for those to increase chance of overlap. \ No newline at end of file diff --git a/kamera/calibration/registration.py b/kamera/calibration/registration.py new file mode 100644 index 00000000..999247c9 --- /dev/null +++ b/kamera/calibration/registration.py @@ -0,0 +1,163 @@ +"""Inter-camera homographies: DIVE camera-registration JSON (v2) and GIF overlays. + +Each ``_to__registration.json`` holds one matrix-only pair whose +``leftToRight`` homography maps left-camera pixels onto right-camera pixels. The +matrix is fit to the calibrated models by casting a grid of left pixels to a nominal +ground range and projecting them into the right camera. The range matters: cameras +whose exposure lags the trigger sit an effective metre or so along track, and that +baseline only vanishes at infinity. The fit residual is reported. +""" + +from __future__ import annotations + +import datetime +import json +import os + +import cv2 +import numpy as np +import PIL.Image + +DIVE_TYPE = "dive-camera-registration" +DIVE_VERSION = 2 + + +def model_homography( + src_cm, dst_cm, range_m: float, grid: int = 40 +) -> tuple[np.ndarray, dict]: + """Least-squares homography from ``src_cm`` pixels to ``dst_cm`` pixels. + + Exact for ground ``range_m`` away from the source camera. Also returns fit stats. + """ + xg, yg = np.meshgrid( + np.linspace(0, src_cm.width - 1, grid), np.linspace(0, src_cm.height - 1, grid) + ) + src = np.vstack([xg.ravel(), yg.ravel()]) + ray_pos, ray_dir = src_cm.unproject(src, -np.inf) + dst = np.asarray( + dst_cm.project(ray_pos + ray_dir * range_m, -np.inf), dtype=np.float64 + ) + inside = ( + np.all(np.isfinite(dst), 0) + & (dst[0] >= 0) + & (dst[0] <= dst_cm.width) + & (dst[1] >= 0) + & (dst[1] <= dst_cm.height) + ) + if inside.sum() < 4: + raise ValueError( + f"only {inside.sum()} of {src.shape[1]} samples land in the destination" + ) + h, _ = cv2.findHomography(src[:, inside].T, dst[:, inside].T, 0) + err = np.linalg.norm( + cv2.perspectiveTransform(src[:, inside].T.reshape(-1, 1, 2), h).reshape(-1, 2) + - dst[:, inside].T, + axis=1, + ) + stats = { + "rmsPx": float(np.sqrt(np.mean(err**2))), + "p95Px": float(np.percentile(err, 95)), + "maxPx": float(np.max(err)), + "coverage": float(inside.mean()), + "rangeM": float(range_m), + } + return h, stats + + +def write_dive_registration( + out_dir: str, left: str, right: str, h: np.ndarray, stats: dict, source: dict +) -> str: + """Write one matrix-only v2 pair file and return its path.""" + inv = np.linalg.inv(h) + pair = { + "left": left, + "right": right, + "transformType": "homography", + "leftToRight": h.tolist(), + "rightToLeft": (inv / inv[2, 2]).tolist(), + "observations": [], + "stats": {f"modelFit{k[0].upper()}{k[1:]}": v for k, v in stats.items()}, + } + body = { + "type": DIVE_TYPE, + "version": DIVE_VERSION, + "source": source, + "pairs": [pair], + } + os.makedirs(out_dir, exist_ok=True) + path = os.path.join(out_dir, f"{left}_to_{right}_registration.json") + with open(path, "w") as f: + json.dump(body, f, indent=2) + return path + + +def source_stamp(flight_dir: str, extra: dict | None = None) -> dict: + stamp = { + "producer": "kamera-rig-calibration", + "flight": os.path.basename(os.path.abspath(flight_dir)), + "generated": datetime.datetime.now(datetime.timezone.utc) + .replace(microsecond=0) + .isoformat() + .replace("+00:00", "Z"), + } + return {**stamp, **(extra or {})} + + +def warp_pair( + left_img: np.ndarray, right_img: np.ndarray, h: np.ndarray, width: int = 1600 +) -> tuple[np.ndarray, np.ndarray, np.ndarray]: + """Warp the left image into the right image's pixels. + + Returns the warped left, the right, and the warped footprint mask, all resized to + ``width`` wide, the images RGB. + """ + scale = width / right_img.shape[1] + size = (width, round(right_img.shape[0] * scale)) + s = np.diag([scale, scale, 1.0]) + warped = cv2.warpPerspective(left_img, s @ h, size, flags=cv2.INTER_LINEAR) + mask = ( + cv2.warpPerspective( + np.full(left_img.shape[:2], 255, np.uint8), + s @ h, + size, + flags=cv2.INTER_NEAREST, + ) + > 0 + ) + right = _rgb(cv2.resize(right_img, size, interpolation=cv2.INTER_AREA)) + return _rgb(warped), right, mask + + +def composite(warped: np.ndarray, right: np.ndarray, mask: np.ndarray) -> np.ndarray: + """The right image with the warped left pasted over its footprint, as DIVE shows it.""" + out = right.copy() + out[mask] = warped[mask] + return out + + +def _rgb(im: np.ndarray) -> np.ndarray: + return cv2.cvtColor(im, cv2.COLOR_GRAY2RGB) if im.ndim == 2 else im[:, :, ::-1] + + +def write_gif(path: str, a: np.ndarray, b: np.ndarray, duration_ms: int = 400) -> None: + PIL.Image.fromarray(a).save( + path, + save_all=True, + append_images=[PIL.Image.fromarray(b)], + duration=duration_ms, + loop=0, + ) + + +def blend_overlay( + warped: np.ndarray, right: np.ndarray, mask: np.ndarray +) -> np.ndarray: + """The right image in colour with a magenta/green blend over the warped footprint. + + Misregistration shows as coloured fringes inside the footprint. + """ + gw = cv2.cvtColor(warped, cv2.COLOR_RGB2GRAY) + gr = cv2.cvtColor(right, cv2.COLOR_RGB2GRAY) + out = right.copy() + out[mask] = np.dstack([gw, gr, gw])[mask] + return out diff --git a/kamera/calibration/report.py b/kamera/calibration/report.py new file mode 100644 index 00000000..3b741ead --- /dev/null +++ b/kamera/calibration/report.py @@ -0,0 +1,470 @@ +"""PDF report: flight summary, cameras, rig geometry, registration overlays.""" + +from __future__ import annotations + +import datetime +import textwrap +from dataclasses import dataclass + +import matplotlib +import numpy as np +from matplotlib.backends.backend_pdf import PdfPages + +matplotlib.use("Agg") +import matplotlib.pyplot as plt + +from kamera.calibration.flight import Frame, InsTrajectory +from kamera.calibration.rig import RigCalibration + +PAGE = (11, 8.5) + +FRAME_NOTES = """A frame is one trigger event. Every camera fires on it and writes one image, so the +images of a trigger share a single rig position and orientation, and that shared pose +is what the rig bundle adjustment enforces. Only triggers where every camera wrote an +image are used. A frame is registered when structure from motion placed it; the +boresight uses the registered frames whose INS-to-rig rotation is not an outlier.""" + + +@dataclass +class FlightSummary: + """What the flight folder held and which of it went into the calibration.""" + + discovered: int # triggers found in the flight folder + complete: int # triggers with an image from every camera + selected: list[Frame] # complete frames handed to structure from motion + selection: str # how they were picked, in words + images_on_disk: dict[str, int] # per camera, over every discovered trigger + ins: InsTrajectory + + +def _utc(t: float) -> datetime.datetime: + return datetime.datetime.fromtimestamp(t, datetime.timezone.utc) + + +def _table_page( + pdf: PdfPages, + title: str, + header: list[str], + rows: list[list], + widths=None, + note: str = "", +) -> None: + fig, ax = plt.subplots(figsize=PAGE) + ax.axis("off") + ax.set_title(title, fontsize=15, weight="bold", loc="left", pad=20) + if note: + fig.text( + 0.06, + 0.86 - 0.033 * (len(rows) + 1) - 0.04, + "\n".join(textwrap.wrap(note, 120)), + fontsize=8, + va="top", + linespacing=1.4, + ) + table = ax.table( + cellText=rows, + colLabels=header, + loc="upper center", + cellLoc="center", + colWidths=widths, + ) + table.auto_set_font_size(False) + table.set_fontsize(8) + table.scale(1, 1.5) + pdf.savefig(fig) + plt.close(fig) + + +def summary_page(pdf: PdfPages, cal: RigCalibration, fs: FlightSummary) -> None: + """Page 1: the flight, the frames, and how many of them each camera contributed.""" + sel = fs.selected + t0, t1 = sel[0].time, sel[-1].time + ins_t0, ins_t1 = fs.ins.times[0], fs.ins.times[-1] + pos = np.array([fs.ins.pose(f.time)[0] for f in sel]) + track_km = np.linalg.norm(np.diff(pos, axis=0), axis=1).sum() / 1000 + window = (fs.ins.times >= t0) & (fs.ins.times <= t1) + alt = fs.ins.llh[window if window.any() else slice(None), 2] + interval = np.median(np.diff([f.time for f in sel])) + registered = np.isin( + np.round([f.time for f in sel], 3), np.round(cal.frame_times, 3) + ) + facts = [ + ("flight", cal.flight), + ("rig", f"{cal.rig}: {len(cal.cameras)} cameras, reference {cal.reference}"), + ( + "date (UTC)", + f"{_utc(ins_t0):%Y-%m-%d}, {_utc(ins_t0):%H:%M} to {_utc(ins_t1):%H:%M} " + f"({(ins_t1 - ins_t0) / 60:.0f} min of INS samples)", + ), + ( + "frames on disk", + f"{fs.discovered} triggers, {fs.complete} with every camera", + ), + ( + "frames selected", + f"{len(sel)} ({fs.selection}), {_utc(t0):%H:%M} to {_utc(t1):%H:%M}, " + f"{(t1 - t0) / 60:.0f} min", + ), + ( + "frames registered", + f"{registered.sum()} placed by SfM, {int(cal.inlier.sum())} used for " + "the boresight", + ), + ("trigger interval", f"median {interval:.2f} s"), + ( + "ground speed", + f"median {cal.ground_speed_mps:.0f} m/s, {track_km:.1f} km flown over " + "the selected frames", + ), + ( + "altitude", + f"INS {alt.min():.0f} to {alt.max():.0f} m above the ellipsoid, " + f"median scene range {cal.scene_range_m:.0f} m", + ), + ] + fig = plt.figure(figsize=PAGE) + fig.text( + 0.05, 0.94, f"KAMERA rig calibration: {cal.rig}", fontsize=16, weight="bold" + ) + width = max(len(k) for k, _ in facts) + fig.text( + 0.05, + 0.88, + "\n".join(f"{k:<{width}} {v}" for k, v in facts), + fontsize=8.5, + va="top", + family="monospace", + linespacing=1.5, + ) + fig.text(0.05, 0.66, "What a frame is", fontsize=11, weight="bold", va="top") + fig.text( + 0.05, + 0.625, + "\n".join(textwrap.wrap(" ".join(FRAME_NOTES.split()), 78)), + fontsize=8.5, + va="top", + linespacing=1.4, + ) + ax = fig.add_axes((0.05, 0.08, 0.5, 0.38)) + ax.axis("off") + ax.set_title("images per camera", fontsize=11, weight="bold", loc="left") + rows = [ + [ + name, + f"{c.width}x{c.height}", + fs.images_on_disk.get(name, 0), + len(sel), + c.frames, + c.observations, + ] + for name, c in sorted(cal.cameras.items()) + ] + table = ax.table( + cellText=rows, + colLabels=[ + "camera", + "size", + "on disk", + "selected", + "registered", + "observations", + ], + loc="upper center", + cellLoc="center", + ) + table.auto_set_font_size(False) + table.set_fontsize(8) + table.scale(1, 1.4) + + ax = fig.add_axes((0.63, 0.42, 0.33, 0.46)) + enu = fs.ins.enu / 1000 + ax.plot(enu[:, 0], enu[:, 1], "-", color="0.8", lw=0.8, label="whole flight") + minutes = (np.array([f.time for f in sel]) - t0) / 60 + sc = ax.scatter( + pos[:, 0] / 1000, pos[:, 1] / 1000, c=minutes, s=6, cmap="viridis", zorder=3 + ) + if not registered.all(): + ax.plot( + pos[~registered, 0] / 1000, + pos[~registered, 1] / 1000, + ".", + color="red", + ms=3, + zorder=4, + label="selected, not registered", + ) + # Zoom to the registered frames: the ferry legs run off the plot. + area = pos[registered] if registered.any() else pos + lo, hi = area[:, :2].min(0) / 1000, area[:, :2].max(0) / 1000 + margin = 0.1 * max(hi - lo) + 0.1 + ax.set( + xlim=(lo[0] - margin, hi[0] + margin), + ylim=(lo[1] - margin, hi[1] + margin), + xlabel="east (km)", + ylabel="north (km)", + title="flight track", + ) + ax.set_aspect("equal") + ax.legend(fontsize=7, loc="best") + fig.colorbar(sc, ax=ax, fraction=0.04, pad=0.02).set_label( + "minutes since first selected frame", fontsize=7 + ) + + ax = fig.add_axes((0.63, 0.08, 0.33, 0.22)) + ax.plot((fs.ins.times - ins_t0) / 60, fs.ins.llh[:, 2], color="0.4", lw=0.8) + ax.axvspan((t0 - ins_t0) / 60, (t1 - ins_t0) / 60, color="C0", alpha=0.2) + ax.set( + xlabel="minutes since first INS sample", + ylabel="altitude (m)", + title="INS altitude, selected window shaded", + ) + pdf.savefig(fig) + plt.close(fig) + + +MODALITY_ORDER = {"rgb": 0, "uv": 1, "ir": 2} +CHANNEL_ORDER = {"L": 0, "C": 1, "R": 2} + + +def camera_order(name: str) -> tuple[int, int]: + """Sort key: modality first so focal lengths sit side by side, then L, C, R.""" + channel, modality = name.split("_") + return MODALITY_ORDER.get(modality, 9), CHANNEL_ORDER.get(channel, 9) + + +def camera_page(pdf: PdfPages, cal: RigCalibration) -> None: + header = [ + "camera", + "fx", + "fy", + "cx", + "cy", + "k1", + "k2", + "p1", + "p2", + "fov deg (h x v)", + "gsd cm", + "obs", + "rms px", + ] + rows = [] + for name in sorted(cal.cameras, key=camera_order): + c = cal.cameras[name] + fov_h = 2 * np.degrees(np.arctan(c.width / 2 / c.K[0, 0])) + fov_v = 2 * np.degrees(np.arctan(c.height / 2 / c.K[1, 1])) + rows.append( + [ + name, + f"{c.K[0, 0]:.1f}", + f"{c.K[1, 1]:.1f}", + f"{c.K[0, 2]:.1f}", + f"{c.K[1, 2]:.1f}", + f"{c.dist[0]:.3f}", + f"{c.dist[1]:.3f}", + f"{c.dist[2]:.4f}", + f"{c.dist[3]:.4f}", + f"{fov_h:.1f} x {fov_v:.1f}", + f"{100 * cal.scene_range_m / c.K[0, 0]:.1f}", + c.observations, + f"{c.reproj_rms_px:.2f}", + ] + ) + _table_page( + pdf, + f"{cal.rig}: camera intrinsics ({cal.flight})", + header, + rows, + widths=[ + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.065, + 0.065, + 0.065, + 0.065, + 0.11, + 0.06, + 0.07, + 0.06, + ], + note=( + "OpenCV model: fx, fy, cx, cy in pixels; k1, k2 radial and p1, p2 " + "tangential distortion. fov is the full field of view from the focal " + "length and image size. gsd is the ground footprint of one pixel at the " + f"flight's median scene range of {cal.scene_range_m:.0f} m. obs is the " + "number of features with a 3D point; rms is their reprojection error." + ), + ) + + +def swathe_order(name: str) -> tuple[int, int]: + """Sort key: L, C, R first so each swathe's three cameras sit together.""" + channel, modality = name.split("_") + return CHANNEL_ORDER.get(channel, 9), MODALITY_ORDER.get(modality, 9) + + +def rig_page(pdf: PdfPages, cal: RigCalibration) -> None: + fig = plt.figure(figsize=PAGE) + fig.text( + 0.06, + 0.94, + f"Rig geometry relative to {cal.reference}", + fontsize=15, + weight="bold", + ) + ax = fig.add_axes((0.06, 0.5, 0.88, 0.4)) + ax.axis("off") + rows = [] + for name in sorted(cal.cameras, key=swathe_order): + rel = cal.rotation_from_reference(name) + rv, c = rel.as_rotvec(degrees=True), cal.cameras[name].center_in_rig + rows.append( + [ + name, + f"{np.degrees(rel.magnitude()):.3f}", + *[f"{v:+.3f}" for v in rv], + *[f"{v:+.2f}" for v in c], + f"{cal.implied_delay_ms(name):+.0f}", + ] + ) + header = [ + "camera", + "angle (deg)", + "rot x (deg)", + "rot y (deg)", + "rot z (deg)", + "lever arm x (m)", + "lever arm y (m)", + "lever arm z (m)", + "exposure offset (ms)", + ] + table = ax.table( + cellText=rows, + colLabels=header, + loc="upper center", + cellLoc="center", + colWidths=[0.08, 0.09, 0.09, 0.09, 0.09, 0.11, 0.11, 0.11, 0.14], + ) + table.auto_set_font_size(False) + table.set_fontsize(8) + table.scale(1, 1.4) + fig.text( + 0.06, + 0.6, + "\n".join( + textwrap.wrap( + "Rotation of each camera relative to the reference, as a rotation " + "vector in the reference camera's axes (x right, y down the image, " + "z along the optical axis); angle is its magnitude. Lever arm is the " + "camera centre in that frame. Exposure offset reads the along-track " + "part of the lever arm as a timing difference at the flight's ground " + "speed, positive when the camera exposes after the reference; a " + "bundle adjustment on a moving rig cannot separate the two. Lever arms " + "are weakly determined at these ranges and should be read as such.", + 125, + ) + ), + fontsize=8, + va="top", + linespacing=1.4, + ) + + ax3 = fig.add_axes((0.2, 0.0, 0.6, 0.46), projection="3d") + # Draw the rig as mounted, in INS body axes (forward, right, down) via the + # boresight: the cameras hang from the mount plate and look down, so the down + # axis is inverted to point down the page. + grid = np.array([[-1, -1], [1, -1], [1, 1], [-1, 1]], float) + ax3.plot_trisurf(grid[:, 0], grid[:, 1], np.zeros(4), color="0.85", alpha=0.5) + colours = {"rgb": "C0", "uv": "C2", "ir": "C3"} + for name in sorted(cal.cameras, key=swathe_order): + z = cal.ins_from_rig.apply(cal.cameras[name].rig_from_cam.apply([0, 0, 1])) + ax3.quiver( + 0, + 0, + 0, + *z, + length=1.0, + label=name, + arrow_length_ratio=0.06, + color=colours.get(name.split("_")[1], "k"), + ) + ax3.set( + xlim=(-1, 1), + ylim=(-1, 1), + zlim=(1, 0), + xticks=[], + yticks=[], + zticks=[], + ) + ax3.set_xlabel("forward", fontsize=8) + ax3.set_ylabel("right (starboard)", fontsize=8) + ax3.set_zlabel("down", fontsize=8) + ax3.tick_params(labelsize=7) + ax3.view_init(elev=22, azim=20) + ax3.set_title( + "optical axes in aircraft body axes, seen from behind the aircraft", + fontsize=10, + y=0.98, + ) + ax3.legend(fontsize=7, loc="center left", bbox_to_anchor=(1.12, 0.5)) + pdf.savefig(fig) + plt.close(fig) + + +def homography_page(pdf: PdfPages, cal: RigCalibration, pair: dict) -> None: + s, left, right = pair["stats"], pair["left"], pair["right"] + # The fit residual is in right-camera pixels; restate it in the left camera's own + # pixels and on the ground, since one IR pixel is many RGB pixels. + scale = cal.cameras[right].K[0, 0] / cal.cameras[left].K[0, 0] + gsd_cm = 100 * s["rangeM"] / cal.cameras[right].K[0, 0] + fig = plt.figure(figsize=PAGE) + fig.text( + 0.03, + 0.965, + f"{left} -> {right} at {s['rangeM']:.0f} m: " + f"fit rms {s['rmsPx']:.2f} px, p95 {s['p95Px']:.2f} px, " + f"max {s['maxPx']:.2f} px in {right} pixels, coverage {100 * s['coverage']:.0f}%", + fontsize=11, + weight="bold", + ) + fig.text( + 0.03, + 0.94, + f"in {left} pixels: rms {s['rmsPx'] / scale:.2f}, " + f"p95 {s['p95Px'] / scale:.2f}, max {s['maxPx'] / scale:.2f} " + f"(one {left} pixel = {scale:.1f} {right} pixels); " + f"rms {s['rmsPx'] * gsd_cm:.0f} cm on the ground", + fontsize=9, + ) + ax = fig.add_axes((0.03, 0.06, 0.94, 0.835)) + # No GIF frame had both images (or --gif_frames 0): keep the page for its fit. + if "overlay_img" in pair: + ax.imshow(pair["overlay_img"]) + ax.set_title( + f"{pair['right']} in colour; inside the {pair['left']} footprint, " + f"{pair['left']} warped in magenta over {pair['right']} in green", + fontsize=8, + ) + ax.axis("off") + h_text = np.array2string( + np.asarray(pair["h"]), precision=5, suppress_small=True, max_line_width=200 + ).replace("\n", " ") + fig.text( + 0.03, 0.03, "H (left -> right) = " + h_text, fontsize=7, family="monospace" + ) + pdf.savefig(fig, dpi=150) + plt.close(fig) + + +def write_report( + path: str, cal: RigCalibration, pairs: list[dict], flight: FlightSummary +) -> None: + with PdfPages(path) as pdf: + summary_page(pdf, cal, flight) + camera_page(pdf, cal) + rig_page(pdf, cal) + for pair in pairs: + homography_page(pdf, cal, pair) diff --git a/kamera/calibration/rig.py b/kamera/calibration/rig.py new file mode 100644 index 00000000..768bc405 --- /dev/null +++ b/kamera/calibration/rig.py @@ -0,0 +1,394 @@ +"""Turn the rigged reconstruction into the deliverables: per-camera models (in the INS +frame), the rig geometry, and the INS boresight with its per-frame residuals.""" + +from __future__ import annotations + +import datetime +import json +import os +import shutil +from dataclasses import dataclass, field + +import numpy as np +import pycolmap as pc +import yaml +from scipy.spatial.transform import Rotation + +from kamera.calibration.flight import InsTrajectory +from kamera.calibration.sfm import robust_mean + + +@dataclass +class CameraCalibration: + name: str + width: int + height: int + K: np.ndarray + dist: np.ndarray + cam_from_rig: pc.Rigid3d + colmap_params: dict + frames: int + # 2D features with a 3D point. Collapses for a camera whose rig seed was too far + # off for its tracks to survive triangulation, while reproj_rms_px stays small. + observations: int + reproj_rms_px: float + + @property + def rig_from_cam(self) -> Rotation: + return Rotation.from_quat(self.cam_from_rig.rotation.quat).inv() + + @property + def center_in_rig(self) -> np.ndarray: + return self.cam_from_rig.inverse().translation + + +@dataclass +class RigCalibration: + rig: str + flight: str + reference: str + cameras: dict[str, CameraCalibration] + ins_from_rig: Rotation + lever_arm_m: np.ndarray + frame_times: np.ndarray + rotation_residual_deg: ( + np.ndarray + ) # (N, 3) rotvec of each frame's boresight about the mean, rig axes + position_residual_m: ( + np.ndarray + ) # (N, 3) rig origin relative to INS, body axes, minus the lever arm + ins_gap_s: np.ndarray # (N,) staleness of the INS sample behind each frame + ground_speed_mps: float + scene_range_m: float # median distance from the reference camera to its 3D points + inlier: np.ndarray = field(default_factory=lambda: np.zeros(0, bool)) + + def camera_quaternion(self, name: str) -> np.ndarray: + """(x, y, z, w) rotating camera vectors into the INS body frame (yaml order).""" + return (self.ins_from_rig * self.cameras[name].rig_from_cam).as_quat() + + def center_in_ins_body(self, name: str) -> np.ndarray: + """Camera centre in INS body axes (forward, right, down) from the rig origin.""" + return self.ins_from_rig.apply(self.cameras[name].center_in_rig) + + def camera_position(self, name: str) -> np.ndarray: + return self.lever_arm_m + self.center_in_ins_body(name) + + def rotation_from_reference(self, name: str) -> Rotation: + """Rotation of a camera relative to the reference camera, in reference axes.""" + return ( + self.cameras[self.reference].rig_from_cam.inv() + * self.cameras[name].rig_from_cam + ) + + def implied_delay_ms(self, name: str) -> float: + """Exposure midpoint relative to the reference camera, from the forward offset. + + Positive means it exposes later than the reference. Only this relative timing is + observable: the position priors absorb any delay common to the whole rig. + """ + return 1000.0 * float(self.center_in_ins_body(name)[0]) / self.ground_speed_mps + + +def per_camera_reprojection( + model: pc.Reconstruction, names: dict +) -> dict[str, list[float]]: + errors: dict[str, list[float]] = {} + for im in model.images.values(): + if not im.has_pose: + continue + cam = names[im.name][0] + for p in im.points2D: + if p.has_point3D(): + proj = im.project_point(model.points3D[p.point3D_id].xyz) + if proj is not None: + errors.setdefault(cam, []).append( + float(np.linalg.norm(proj - p.xy)) + ) + return errors + + +def reference_scene_ranges( + model: pc.Reconstruction, names: dict, reference: str, stride: int = 50 +) -> list[float]: + """Distance from the reference camera to every ``stride``-th of its 3D points.""" + ranges = [] + for im in model.images.values(): + if not im.has_pose or names[im.name][0] != reference: + continue + center = im.projection_center() + for p in im.points2D[::stride]: + if p.has_point3D(): + ranges.append( + float(np.linalg.norm(model.points3D[p.point3D_id].xyz - center)) + ) + return ranges + + +def calibrate_rig( + model: pc.Reconstruction, + names: dict, + ins: InsTrajectory, + reference: str, + rig_name: str, + flight: str, +) -> RigCalibration: + """Read the rig geometry out of the reconstruction and solve the INS boresight.""" + rig = next(iter(model.rigs.values())) + errors = per_camera_reprojection(model, names) + cameras: dict[str, CameraCalibration] = {} + for im in model.images.values(): + if not im.has_pose: + continue + name = names[im.name][0] + if name not in cameras: + cam = model.cameras[im.camera_id] + cam_from_rig = ( + pc.Rigid3d() + if rig.is_ref_sensor(cam.sensor_id) + else rig.sensor_from_rig(cam.sensor_id) + ) + fx, fy, cx, cy, k1, k2, p1, p2 = cam.params + cameras[name] = CameraCalibration( + name=name, + width=cam.width, + height=cam.height, + K=np.array([[fx, 0, cx], [0, fy, cy], [0, 0, 1]]), + dist=np.array([k1, k2, p1, p2]), + cam_from_rig=cam_from_rig, + colmap_params={ + "model": cam.model_name, + "params": [float(v) for v in cam.params], + }, + frames=0, + observations=len(errors.get(name, [])), + reproj_rms_px=float( + np.sqrt(np.mean(np.square(errors.get(name, [np.nan])))) + ), + ) + cameras[name].frames += 1 + + # Per frame: the boresight (INS body <- rig) and the rig origin relative to the INS + # position, in body axes. + times, ins_from_rig, lever, gaps = [], [], [], [] + for frame in model.frames.values(): + if not frame.has_pose(): + continue + any_image = model.images[ + next(iter(frame.data_ids)).id + ] # every image in a frame shares the trigger time + t = names[any_image.name][1] + world_from_rig = frame.rig_from_world.inverse() + pos, enu_from_body = ins.pose(t) + times.append(t) + ins_from_rig.append( + ( + enu_from_body.inv() * Rotation.from_quat(world_from_rig.rotation.quat) + ).as_quat() + ) + lever.append(enu_from_body.inv().apply(world_from_rig.translation - pos)) + gaps.append(ins.sample_gap(t)) + order = np.argsort(times) + times = np.array(times)[order] + rotations = Rotation.from_quat(np.array(ins_from_rig)[order]) + lever = np.array(lever)[order] + gaps = np.array(gaps)[order] + mean_rot, mean_lever, _, keep = robust_mean(rotations, lever) + positions = np.array([ins.pose(t)[0] for t in times]) + speed = float( + np.median(np.linalg.norm(np.diff(positions, axis=0), axis=1) / np.diff(times)) + ) + return RigCalibration( + rig=rig_name, + flight=flight, + reference=reference, + cameras=cameras, + ins_from_rig=mean_rot, + lever_arm_m=mean_lever, + frame_times=times, + rotation_residual_deg=(mean_rot.inv() * rotations).as_rotvec(degrees=True), + position_residual_m=lever - mean_lever, + ins_gap_s=gaps, + ground_speed_mps=speed, + scene_range_m=float(np.median(reference_scene_ranges(model, names, reference))), + inlier=keep, + ) + + +def _floats(a) -> list[float]: + return [float(v) for v in np.asarray(a).ravel()] + + +def _today() -> str: + return datetime.datetime.now(datetime.timezone.utc).date().isoformat() + + +def write_camera_yaml(cal: RigCalibration, name: str, path: str) -> None: + """KAMERA ``standard`` camera model plus rig and calibration provenance. + + The loader ignores the extra keys. + """ + cam = cal.cameras[name] + body = { + "model_type": "standard", + "image_width": int(cam.width), + "image_height": int(cam.height), + "fx": float(cam.K[0, 0]), + "fy": float(cam.K[1, 1]), + "cx": float(cam.K[0, 2]), + "cy": float(cam.K[1, 2]), + "distortion_coefficients": _floats(cam.dist), + "camera_quaternion": _floats(cal.camera_quaternion(name)), + "camera_position": _floats(cal.camera_position(name)), + "camera_name": name, + "channel": name.split("_")[0], + "modality": name.split("_")[1], + "rig": cal.rig, + "reference_camera": cal.reference, + "cam_from_rig": { + "quaternion_xyzw": _floats(cam.cam_from_rig.rotation.quat), + "translation_m": _floats(cam.cam_from_rig.translation), + }, + "colmap_camera": cam.colmap_params, + "calibration": { + "flight": cal.flight, + "generated": _today(), + "frames": cam.frames, + "observations": cam.observations, + "reprojection_rms_px": cam.reproj_rms_px, + "ifov_deg": float(np.degrees(1.0 / cam.K[0, 0])), + }, + } + header = ( + "# KAMERA camera model. camera_quaternion (x, y, z, w) rotates camera\n" + "# vectors into the INS body frame; camera_position is the camera centre in\n" + "# that frame (metres). distortion_coefficients follow OpenCV (k1, k2, p1,\n" + "# p2). The extra keys record the rig calibration this came from.\n" + ) + with open(path, "w") as f: + f.write(header) + yaml.safe_dump(body, f, sort_keys=False) + + +def write_rig_yaml(cal: RigCalibration, path: str) -> None: + cams = {} + for name, cam in cal.cameras.items(): + rel = cal.rotation_from_reference(name) + cams[name] = { + "cam_from_rig": { + "quaternion_xyzw": _floats(cam.cam_from_rig.rotation.quat), + "translation_m": _floats(cam.cam_from_rig.translation), + }, + "rotation_from_reference_deg": _floats(rel.as_rotvec(degrees=True)), + "angle_from_reference_deg": float(np.degrees(rel.magnitude())), + "centre_in_rig_m": _floats(cam.center_in_rig), + "centre_in_ins_body_m": _floats(cal.center_in_ins_body(name)), + "exposure_offset_from_reference_ms": cal.implied_delay_ms(name), + "frames": cam.frames, + "observations": cam.observations, + "reprojection_rms_px": cam.reproj_rms_px, + } + keep = cal.inlier + res = np.linalg.norm(cal.rotation_residual_deg[keep], axis=1) + body = { + "rig": cal.rig, + "flight": cal.flight, + "reference_camera": cal.reference, + "generated": _today(), + "ins_from_rig": { + "quaternion_xyzw": _floats(cal.ins_from_rig.as_quat()), + "rotvec_deg": _floats(cal.ins_from_rig.as_rotvec(degrees=True)), + "euler_zyx_deg": _floats(cal.ins_from_rig.as_euler("ZYX", degrees=True)), + "lever_arm_m": _floats(cal.lever_arm_m), + }, + "flight_stats": { + "ground_speed_mps": cal.ground_speed_mps, + "scene_range_m": cal.scene_range_m, + }, + "boresight_quality": { + "frames": int(keep.sum()), + "frames_rejected": int((~keep).sum()), + "rotation_scatter_deg": { + "median": float(np.median(res)), + "p90": float(np.percentile(res, 90)), + "max": float(res.max()), + }, + "rotation_axis_std_deg": _floats(cal.rotation_residual_deg[keep].std(0)), + "lever_arm_std_m": _floats(cal.position_residual_m[keep].std(0)), + "ins_sample_gap_s": { + "median": float(np.median(cal.ins_gap_s)), + "max": float(cal.ins_gap_s.max()), + }, + }, + "cameras": cams, + } + with open(path, "w") as f: + f.write( + "# Rig geometry (cam_from_rig maps rig -> camera, COLMAP convention) and\n" + "# INS boresight (ins_from_rig maps rig -> INS body). A camera exposing\n" + "# later than the reference sits ahead along track by speed x delay;\n" + "# exposure_offset_from_reference_ms reads that off.\n" + ) + yaml.safe_dump(body, f, sort_keys=False) + + +def camera_yaml_path(cal: RigCalibration, name: str, out_dir: str) -> str: + return os.path.join(out_dir, f"{cal.rig}_{name}.yaml") + + +def write_outputs(cal: RigCalibration, out_dir: str) -> list[str]: + """Write one yaml per camera plus the rig yaml; returns the paths written.""" + os.makedirs(out_dir, exist_ok=True) + paths = [] + for name in sorted(cal.cameras): + path = camera_yaml_path(cal, name, out_dir) + write_camera_yaml(cal, name, path) + paths.append(path) + rig_path = os.path.join(out_dir, f"{cal.rig}_rig.yaml") + write_rig_yaml(cal, rig_path) + paths.append(rig_path) + return paths + + +# Camera channel -> the field-of-view name postflight uses in its sys_config.json keys. +SYS_CONFIG_FOV = {"L": "left", "C": "center", "R": "right"} + + +def write_sys_configs( + cal: RigCalibration, out_dir: str, config_dirs: list[str], install: bool +) -> list[str]: + """A postflight ``sys_config.json`` per system configuration directory of the + flight, pointing ``__yaml_path`` at the calibrated camera models. + + Postflight (flight summary, footprint KMLs, geotiffs) finds its camera models + through ``/sys_config.json``. Each file written here is the flight's + own one with only those keys replaced, saved as + ``out_dir/_sys_config.json``. With ``install`` it also replaces + the flight's file, keeping the original as ``sys_config.json.orig``. Returns the + paths written. + """ + models = {} + for name in cal.cameras: + channel, modality = name.split("_") + if channel in SYS_CONFIG_FOV: + key = f"{SYS_CONFIG_FOV[channel]}_{modality}_yaml_path" + models[key] = os.path.abspath(camera_yaml_path(cal, name, out_dir)) + paths = [] + for config_dir in sorted(config_dirs): + flight_path = os.path.join(config_dir, "sys_config.json") + body = {} + if os.path.exists(flight_path): + with open(flight_path) as f: + body = json.load(f) + body.update(models) + name = os.path.basename(os.path.normpath(config_dir)) + path = os.path.join(out_dir, f"{name}_sys_config.json") + with open(path, "w") as f: + json.dump(body, f, indent=4, sort_keys=True) + paths.append(path) + if install: + backup = flight_path + ".orig" + if os.path.exists(flight_path) and not os.path.exists(backup): + shutil.copy2(flight_path, backup) + shutil.copy2(path, flight_path) + paths.append(flight_path) + return paths diff --git a/kamera/calibration/sfm.py b/kamera/calibration/sfm.py new file mode 100644 index 00000000..5ae7c2b2 --- /dev/null +++ b/kamera/calibration/sfm.py @@ -0,0 +1,369 @@ +"""Structure from motion with pycolmap: features, INS position priors, matching, and the +two mapping passes (trivial rigs to bootstrap, then the full multi-sensor rig).""" + +from __future__ import annotations + +import os +import shutil + +import cv2 +import numpy as np +import PIL.Image +import pycolmap as pc +from scipy.spatial.transform import Rotation + +CAMERA_MODEL = "OPENCV" +# Only image headers are read here; the 100 MP RGB frames trip PIL's default bomb limit. +PIL.Image.MAX_IMAGE_PIXELS = None + + +def device() -> pc.Device: + return pc.Device.cuda if pc.has_cuda else pc.Device.cpu + + +def extract_features( + db_path: str, + image_dir: str, + names: dict, + focal_px: dict, + distortion: dict, + max_image_size: int, + num_features: int, +) -> None: + """SIFT per camera folder, seeding each camera with its modality's intrinsics.""" + for camera in sorted({c for c, _ in names.values()}): + image_names = sorted(n for n in names if n.startswith(camera + "/")) + w, h = PIL.Image.open(os.path.join(image_dir, image_names[0])).size + modality = camera.split("_")[1] + f, (k1, k2) = focal_px[modality], distortion[modality] + reader = pc.ImageReaderOptions( + camera_model=CAMERA_MODEL, + camera_params=f"{f},{f},{w / 2},{h / 2},{k1},{k2},0,0", + ) + # Each thread decodes a full-resolution image; large sensors get fewer threads. + opts = pc.FeatureExtractionOptions( + max_image_size=max_image_size, + use_gpu=pc.has_cuda, + num_threads=4 if w * h > 40e6 else 16, + ) + opts.sift.max_num_features = num_features + pc.extract_features( + db_path, + image_dir, + image_names=image_names, + camera_mode=pc.CameraMode.PER_FOLDER, + reader_options=reader, + extraction_options=opts, + device=device(), + ) + + +def write_pose_priors(db_path: str, names: dict, ins, std_m: float) -> None: + """Attach the INS ENU position at each image's trigger time as a pose prior.""" + db = pc.Database.open(db_path) + for image in db.read_all_images(): + prior = pc.PosePrior( + position=ins.pose(names[image.name][1])[0], + position_covariance=np.eye(3) * std_m**2, + coordinate_system=pc.PosePriorCoordinateSystem.CARTESIAN, + ) + prior.corr_data_id = pc.data_t( + pc.sensor_t(pc.SensorType.CAMERA, image.camera_id), image.image_id + ) + db.write_pose_prior(prior) + db.close() + + +def match_features(db_path: str, max_distance_m: float, max_neighbors: int) -> None: + """Match each image against its spatial neighbours (from the priors). + + Pairs are formed across all cameras. + """ + pairing = pc.SpatialPairingOptions( + max_num_neighbors=max_neighbors, max_distance=max_distance_m, ignore_z=True + ) + pc.match_spatial( + db_path, + matching_options=pc.FeatureMatchingOptions(use_gpu=pc.has_cuda), + pairing_options=pairing, + device=device(), + ) + prune_cross_spectral(db_path) + + +def prune_cross_spectral(db_path: str) -> int: + """Drop thermal-to-visible pairs. + + SIFT cannot match them, so their few 'inliers' only mislead the mapper. + """ + db = pc.Database.open(db_path) + is_ir = { + im.image_id: im.name.split("/")[0].endswith("_ir") + for im in db.read_all_images() + } + pair_ids, _ = db.read_two_view_geometries() + dropped = 0 + for pair_id in pair_ids: + i, j = pc.pair_id_to_image_pair(pair_id) + if is_ir[i] != is_ir[j]: + db.delete_matches(i, j) + db.delete_two_view_geometry(i, j) + dropped += 1 + db.close() + return dropped + + +def mapping_options(refine_rig: bool) -> pc.IncrementalPipelineOptions: + # Colours are unused and extracting them re-decodes every 100 MP frame. + opts = pc.IncrementalPipelineOptions( + use_prior_position=True, + ba_refine_sensor_from_rig=refine_rig, + extract_colors=False, + # Distortion cannot be recovered from two or three views of flat ground: on + # the May 2025 flight, refining it from the initial pair drove L_ir to a 30% + # focal error and k2 of -3, so no L_ir model ever grew past three images. + # It stays at the per-modality seed; pass 2 refines the full intrinsics once + # the whole rig is posed. + ba_refine_extra_params=False, + ) + # Nadir aerial pairs subtend small angles; the default 16 deg init threshold + # rejects them. + opts.mapper.init_min_tri_angle = 4.0 + # Global BA every 30% of growth instead of 10%: it dominates runtime on thousands + # of frames. + opts.ba_global_frames_ratio = opts.ba_global_points_ratio = 1.3 + opts.ba_global_max_refinements = 2 + return opts + + +def run_mapping( + db_path: str, image_dir: str, out_dir: str +) -> dict[int, pc.Reconstruction]: + """Incremental mapping from scratch with every camera independent (trivial rigs).""" + shutil.rmtree(out_dir, ignore_errors=True) + os.makedirs(out_dir) + return pc.incremental_mapping( + db_path, image_dir, out_dir, options=mapping_options(refine_rig=False) + ) + + +def rig_bundle_adjust( + model: pc.Reconstruction, priors: list, refine_intrinsics: bool, max_iterations: int +) -> str: + """Refine rig poses, sensor_from_rig and optionally intrinsics. + + Anchored to the INS position priors. + """ + opts = pc.BundleAdjustmentOptions( + refine_sensor_from_rig=True, + refine_rig_from_world=True, + refine_principal_point=False, + refine_focal_length=refine_intrinsics, + refine_extra_params=refine_intrinsics, + print_summary=False, + ) + opts.ceres.solver_options.max_num_iterations = max_iterations + config = pc.BundleAdjustmentConfig() + for image in model.images.values(): + if image.has_pose: + config.add_image(image.image_id) + prior_opts = pc.PosePriorBundleAdjustmentOptions() + prior_opts.alignment_ransac.max_error = 5.0 + summary = pc.create_pose_prior_bundle_adjuster( + opts, prior_opts, config, priors, model + ).solve() + model.update_point_3d_errors() + return summary.brief_report() + + +def refine_rig( + db_path: str, names: dict, init_dir: str, out_dir: str, max_iterations: int = 200 +) -> pc.Reconstruction: + """Pass 2: triangulate every image from the rig poses, bundle adjust, retriangulate, + and bundle adjust again with the intrinsics free. + + Returns the final model, also written to ``out_dir``. + """ + shutil.rmtree(out_dir, ignore_errors=True) + os.makedirs(out_dir) + # The triangulator always colours points from disk; 8x8 stand-ins spare it the + # 100 MP frames. + image_dir = os.path.join(os.path.dirname(out_dir), "placeholders") + for name in names: + os.makedirs(os.path.dirname(os.path.join(image_dir, name)), exist_ok=True) + cv2.imwrite(os.path.join(image_dir, name), np.zeros((8, 8, 3), np.uint8)) + db = pc.Database.open(db_path) + priors = db.read_all_pose_priors() + db.close() + opts = mapping_options(refine_rig=True) + model = pc.Reconstruction(init_dir) + for refine_intrinsics in (False, True): + # Intrinsics are only ever refined in the rig bundle adjustment below, never by + # the triangulator. + model = pc.triangulate_points( + model, + db_path, + image_dir, + out_dir, + clear_points=True, + options=opts, + refine_intrinsics=False, + ) + print( + f" triangulated {model.num_points3D()} points, " + f"{model.compute_mean_reprojection_error():.2f} px", + flush=True, + ) + print( + f" {rig_bundle_adjust(model, priors, refine_intrinsics, max_iterations)}", + flush=True, + ) + model.write(out_dir) + return model + + +def load_models(out_dir: str) -> dict[int, pc.Reconstruction]: + return { + int(d): pc.Reconstruction(os.path.join(out_dir, d)) + for d in sorted(os.listdir(out_dir)) + if d.isdigit() + } + + +def image_poses( + models: dict[int, pc.Reconstruction], names: dict +) -> dict[tuple[str, float], pc.Rigid3d]: + """``{(camera, time): cam_from_world}`` over every posed image in every model. + + All models are in INS ENU. + """ + return { + names[im.name]: im.cam_from_world() + for r in models.values() + for im in r.images.values() + if im.has_pose + } + + +def robust_mean( + rotations: Rotation, translations: np.ndarray, cluster_deg: float = 1.0 +) -> tuple[Rotation, np.ndarray, np.ndarray, np.ndarray]: + """Mean rotation of the densest cluster and median translation over its members. + + Seeds from the sample with the most neighbours within ``cluster_deg``, so a wrongly + registered majority (a folded sub-model) cannot drag the estimate; then keeps + everything within 3x that cluster's median residual. Returns mean, translation, + per-sample residual angles (deg) and the inlier mask. + """ + q = rotations.as_quat() + pairwise = np.degrees(2.0 * np.arccos(np.clip(np.abs(q @ q.T), 0.0, 1.0))) + keep = pairwise[np.argmax((pairwise < cluster_deg).sum(1))] < cluster_deg + mean = rotations[keep].mean() + angles = np.degrees((mean.inv() * rotations).magnitude()) + keep = angles <= max(3.0 * np.median(angles[keep]), 0.05) + mean = rotations[keep].mean() + return ( + mean, + np.median(translations[keep], 0), + np.degrees((mean.inv() * rotations).magnitude()), + keep, + ) + + +def derive_rig( + models: dict[int, pc.Reconstruction], names: dict, reference: str +) -> dict[str, dict]: + """Initial ``cam_from_rig`` per camera from frames shared with the reference.""" + poses = image_poses(models, names) + rig = {} + for camera in sorted({c for c, _ in names.values()}): + rel = [ + poses[(camera, t)] * poses[(reference, t)].inverse() + for (c, t) in poses + if c == camera and (reference, t) in poses + ] + if len(rel) < 3: + raise RuntimeError( + f"{camera}: only {len(rel)} frames shared with {reference}; " + "cannot initialise the rig" + ) + rotations = Rotation.from_quat([x.rotation.quat for x in rel]) + translations = np.array([x.translation for x in rel]) + rot, trans, angles, keep = robust_mean(rotations, translations) + rig[camera] = { + "cam_from_rig": pc.Rigid3d(pc.Rotation3d(rot.as_quat()), trans), + "frames": int(keep.sum()), + "frames_total": len(rel), + "rotation_scatter_deg": float(np.median(angles[keep])), + "translation_std_m": translations[keep].std(0), + } + return rig + + +def model_cameras( + models: dict[int, pc.Reconstruction], names: dict +) -> dict[str, pc.Camera]: + """Refined intrinsics per camera name from whichever model registered it.""" + cams = {} + for r in models.values(): + for im in r.images.values(): + if im.has_pose: + cams.setdefault(names[im.name][0], r.cameras[im.camera_id]) + return cams + + +def rigged_model( + db_path: str, + models: dict[int, pc.Reconstruction], + names: dict, + reference: str, + out_dir: str, +) -> pc.Reconstruction: + """Put the rig onto the largest pass-1 model and fill its frames with every image. + + Writes the rig and frames into the database, copies each registered frame's pose + from the model, and adds the images (IR, typically) that pass 1 never posed: they + inherit their pose from the frame through the initial ``cam_from_rig``. The result + is written to ``out_dir`` as the starting point for the rig-refining mapping pass. + """ + rig = derive_rig(models, names, reference) + cameras = model_cameras(models, names) + config = pc.RigConfig( + cameras=[ + pc.RigConfigCamera( + ref_sensor=(name == reference), + image_prefix=name + "/", + camera=cameras[name], + cam_from_rig=None if name == reference else rig[name]["cam_from_rig"], + ) + for name in [reference] + sorted(set(rig) - {reference}) + ] + ) + model = largest(models) + for cam in cameras.values(): + if not model.exists_camera(cam.camera_id): + model.add_camera(cam) + db = pc.Database.open(db_path) + db.clear_frames() + db.clear_rigs() + pc.apply_rig_config([config], db, model) + poses = { + f.frame_id: f.rig_from_world for f in model.frames.values() if f.has_pose() + } + frames = db.read_all_frames() + for frame in frames: + if frame.frame_id in poses: + frame.rig_from_world = poses[frame.frame_id] + model.set_rigs_and_frames(db.read_all_rigs(), frames) + for image in db.read_all_images(): + if not model.exists_image(image.image_id): + model.add_image(image) + db.close() + shutil.rmtree(out_dir, ignore_errors=True) + os.makedirs(out_dir) + model.write(out_dir) + return model + + +def largest(models: dict[int, pc.Reconstruction]) -> pc.Reconstruction: + return max(models.values(), key=lambda r: r.num_reg_images()) diff --git a/kamera/colmap_processing/camera_models.py b/kamera/colmap_processing/camera_models.py index c1211f85..02497fab 100644 --- a/kamera/colmap_processing/camera_models.py +++ b/kamera/colmap_processing/camera_models.py @@ -4,37 +4,28 @@ import cv2 import time import yaml -from scipy.interpolate import interp1d, RectBivariateSpline -from scipy.optimize import fmin, fminbound, minimize +from scipy.interpolate import RectBivariateSpline +from scipy.spatial.transform import Rotation import PIL from math import sqrt -import matplotlib.pyplot as plt - # Repository imports. -from kamera.colmap_processing.image_renderer import stitch_images from kamera.colmap_processing.platform_pose import PlatformPoseFixed from kamera.colmap_processing.geo_conversions import enu_to_llh, llh_to_enu from kamera.colmap_processing.rotations import ( - euler_from_quaternion, - quaternion_multiply, - quaternion_matrix, - quaternion_from_euler, - quaternion_inverse, - quaternion_from_matrix, - ) -import kamera.colmap_processing.dp as dp + quaternion_matrix, + quaternion_inverse, + quaternion_from_matrix, +) def to_str(v): - """Convert numerical values (scalar or float) to string for saving to yaml - - """ + """Convert numerical values (scalar or float) to string for saving to yaml""" if isinstance(v, np.ndarray): v = v.tolist() else: return str(v) - if isinstance(v, list): + if isinstance(v, list): if len(v) > 1: return repr(list(v)) else: @@ -57,13 +48,18 @@ class CamToCamTform(object): the view such that we can ignore parallax during transformation. """ + def __init__(self, src_cm, dst_cm): - if src_cm.platform_pose_provider != dst_cm.platform_pose_provider and \ - not isinstance(src_cm.platform_pose_provider, PlatformPoseFixed) and \ - not isinstance(dst_cm.platform_pose_provider, PlatformPoseFixed): - raise Exception('src_cm and dst_cm must have the same ' - 'platform_pose_provider indicating that the cameras ' - 'are rigidly mounted to the same platform') + if ( + src_cm.platform_pose_provider != dst_cm.platform_pose_provider + and not isinstance(src_cm.platform_pose_provider, PlatformPoseFixed) + and not isinstance(dst_cm.platform_pose_provider, PlatformPoseFixed) + ): + raise Exception( + "src_cm and dst_cm must have the same " + "platform_pose_provider indicating that the cameras " + "are rigidly mounted to the same platform" + ) self._src_cm = src_cm self._dst_cm = dst_cm @@ -84,11 +80,11 @@ def fit(self, tol=0.1, k=1): # the number of tiles. N = 10 while True: - dx = np.sqrt(w*h/N) - x = np.linspace(0, w, int(np.ceil(w/dx))) - y = np.linspace(0, h, int(np.ceil(h/dx))) - X,Y = np.meshgrid(x, y) - points = np.vstack([X.ravel(),Y.ravel()]) + dx = np.sqrt(w * h / N) + x = np.linspace(0, w, int(np.ceil(w / dx))) + y = np.linspace(0, h, int(np.ceil(h / dx))) + X, Y = np.meshgrid(x, y) + points = np.vstack([X.ravel(), Y.ravel()]) out_points = self.tform_rigorous(points) @@ -99,14 +95,14 @@ def fit(self, tol=0.1, k=1): self._model_y = RectBivariateSpline(x, y, out_y.T, kx=k, ky=k) # Test - x = np.linspace(0, w, int(np.ceil(w/dx))*2) - y = np.linspace(0, h, int(np.ceil(h/dx))*2) - X,Y = np.meshgrid(x, y) - points = np.vstack([X.ravel(),Y.ravel()]) + x = np.linspace(0, w, int(np.ceil(w / dx)) * 2) + y = np.linspace(0, h, int(np.ceil(h / dx)) * 2) + X, Y = np.meshgrid(x, y) + points = np.vstack([X.ravel(), Y.ravel()]) points_out = self.tform(points) points_out_truth = self.tform_rigorous(points) - err = np.sqrt(np.sum((points_out_truth - points_out)**2, 0)) + err = np.sqrt(np.sum((points_out_truth - points_out) ** 2, 0)) if np.max(err) < tol or N > 2000: break @@ -123,8 +119,8 @@ def tform(self, points): :rtype: numpy.ndarray of size (2,n) """ - if not hasattr(self, '_model_x'): - raise Exception('Must call \'fit\' before calling \'tform\'') + if not hasattr(self, "_model_x"): + raise Exception("Must call 'fit' before calling 'tform'") out_points = np.zeros_like(points) out_points[0] = self._model_x.ev(points[0], points[1]) @@ -148,7 +144,7 @@ def tform_rigorous(self, points): # We don't have a world model to intersect with, so we send it out # to "infinity". - point = (ray_pos + ray_dir*1e5) + point = ray_pos + ray_dir * 1e5 return self._dst_cm.project(point, -np.inf) @@ -182,35 +178,32 @@ def rt_from_quat_pos(position, quaternion): # system. So, we invert each quaternion. quaternion = quaternion_inverse(quaternion) - p = quaternion_matrix(quaternion) # R - p[:3,3] = -np.dot(p[:3,:3], position) # T + p = quaternion_matrix(quaternion) # R + p[:3, 3] = -np.dot(p[:3, :3], position) # T return p def load_from_file(filename, platform_pose_provider=None): - """Load from configuration yaml for any of the Camera subclasses. - - """ - with open(filename, 'r') as f: + """Load from configuration yaml for any of the Camera subclasses.""" + with open(filename, "r") as f: calib = yaml.safe_load(f) - if calib['model_type'] == 'standard': + if calib["model_type"] == "standard": return StandardCamera.load_from_file(filename, platform_pose_provider) - if calib['model_type'] == 'rolling_shutter': + if calib["model_type"] == "rolling_shutter": return RollingShutterCamera.load_from_file(filename, platform_pose_provider) - if calib['model_type'] == 'depth': + if calib["model_type"] == "depth": return DepthCamera.load_from_file(filename, platform_pose_provider) - if calib['model_type'] == 'static': + if calib["model_type"] == "static": return GeoStaticCamera.load_from_file(filename, platform_pose_provider) raise Exception() -def ray_intersect_plane(plane_point, plane_normal, ray_pos, ray_dir, - epsilon=1e-6): +def ray_intersect_plane(plane_point, plane_normal, ray_pos, ray_dir, epsilon=1e-6): """From https://rosettacode.org/wiki/Find_the_intersection_of_a_line_with_a_plane#Python :param ray_pos: Ray starting positions. @@ -260,6 +253,9 @@ class Camera(object): any time-varying parameters (e.g., navigation coordinate system state). """ + + model_type = "standard" + def __init__(self, width, height, platform_pose_provider=None): """ :param width: Width of the image provided by the imaging sensor, @@ -312,35 +308,35 @@ def depth_map(self, value): @property def platform_pose_provider(self): - """Instance of a subclass of NavStateProvider - - """ + """Instance of a subclass of NavStateProvider""" return self._platform_pose_provider @platform_pose_provider.setter def platform_pose_provider(self, value): - """Instance of a subclass of NavStateProvider - - """ + """Instance of a subclass of NavStateProvider""" self._platform_pose_provider = value def __str__(self): - string = [''.join(['image_width: ',repr(self._width),'\n'])] - string.append(''.join(['image_height: ',repr(self._height),'\n'])) - string.append(''.join(['platform_pose_provider: ', - repr(self._platform_pose_provider)])) + string = ["".join(["image_width: ", repr(self._width), "\n"])] + string.append("".join(["image_height: ", repr(self._height), "\n"])) + string.append( + "".join(["platform_pose_provider: ", repr(self._platform_pose_provider)]) + ) try: # Some time-dependent cameras may not have a queue of values. - string.append(''.join(['\nifov: ', - '({:.6g},{:.6g})'.format(*self.ifov(np.inf)), - '\n'])) - string.append(''.join(['fov: ', - '({:.6},{:.6},{:.6})'.format(*self.fov(np.inf))])) - except: + string.append( + "".join( + ["\nifov: ", "({:.6g},{:.6g})".format(*self.ifov(np.inf)), "\n"] + ) + ) + string.append( + "".join(["fov: ", "({:.6},{:.6},{:.6})".format(*self.fov(np.inf))]) + ) + except Exception: pass - return ''.join(string) + return "".join(string) def __repr__(self): return self.__str__() @@ -364,7 +360,7 @@ def get_param_array(self, param_list): """ params = np.zeros(0) for param in param_list: - params = np.hstack([params,getattr(self, param)]) + params = np.hstack([params, getattr(self, param)]) return params @@ -381,8 +377,8 @@ def set_param_array(self, param_list, params): ind = 0 for param in param_list: p0 = getattr(self, param) - if hasattr(p0, '__len__') and len(p0) > 1: - setattr(self, param, params[ind:ind+len(p0)]) + if hasattr(p0, "__len__") and len(p0) > 1: + setattr(self, param, params[ind : ind + len(p0)]) ind += len(p0) else: setattr(self, param, params[ind]) @@ -483,8 +479,9 @@ def unproject_to_llh(self, points, t=None, cov=None): h0 = self.platform_pose_provider.h0 if lat0 is None or lon0 is None or h0 is None: - raise Exception('\'platform_pose_provider\' must have \'lat0\', ' - '\'lon0\', and \'ho\' defined.') + raise Exception( + "'platform_pose_provider' must have 'lat0', 'lon0', and 'ho' defined." + ) points = np.array(points) if points.ndim == 1: @@ -493,13 +490,13 @@ def unproject_to_llh(self, points, t=None, cov=None): else: was_1d = False points = np.array(points) - points = np.reshape(points, (2,-1)) + points = np.reshape(points, (2, -1)) llh = [] geo_cov = [] for i in range(points.shape[1]): - xyz = self.unproject_to_depth(points[:,i], t) + xyz = self.unproject_to_depth(points[:, i], t) if np.all(np.isfinite(xyz)): llh.append(enu_to_llh(xyz[0], xyz[1], xyz[2], lat0, lon0, h0)) else: @@ -508,16 +505,14 @@ def unproject_to_llh(self, points, t=None, cov=None): if cov is not None: # Sample 10 random points and project each into enu coordinate # system - rpoints = np.random.multivariate_normal(points[:,i], cov[i], - 20) + rpoints = np.random.multivariate_normal(points[:, i], cov[i], 20) # Points must be inside image. - ind = np.logical_and(rpoints[:,0] > 0, rpoints[:,1] > 0) - ind = np.logical_and(ind, rpoints[:,0] < self.width) - ind = np.logical_and(ind, rpoints[:,1] < self.height) + ind = np.logical_and(rpoints[:, 0] > 0, rpoints[:, 1] > 0) + ind = np.logical_and(ind, rpoints[:, 0] < self.width) + ind = np.logical_and(ind, rpoints[:, 1] < self.height) rpoints = rpoints[ind] - enu_pts = ([self.unproject_to_depth(_, t).ravel() - for _ in rpoints]) + enu_pts = [self.unproject_to_depth(_, t).ravel() for _ in rpoints] enu_pts = [_ for _ in enu_pts if np.all(np.isfinite(enu_pts))] @@ -527,7 +522,7 @@ def unproject_to_llh(self, points, t=None, cov=None): enu_pts = np.array(enu_pts) - if xyz[0]**2 + xyz[1]**2 > 6250000: + if xyz[0] ** 2 + xyz[1] ** 2 > 6250000: # If the point is further than 2.5km from the camera, we # want the covariance defined in an east/north/up # coordinate system centered at xyz, the most-likely @@ -538,12 +533,17 @@ def unproject_to_llh(self, points, t=None, cov=None): llh0 = enu_to_llh(xyz[0], xyz[1], xyz[2], lat0, lon0, h0) for i in range(len(enu_pts)): - llhi = enu_to_llh(enu_pts[i,0], enu_pts[i,1], - enu_pts[i,2], llh0[0], llh0[1], - llh0[2]) - enu_pts[i,:] = llh_to_enu(llhi[0], llhi[1], llhi[2], - llh0[0], llh0[1], llh0[2]) - + llhi = enu_to_llh( + enu_pts[i, 0], + enu_pts[i, 1], + enu_pts[i, 2], + llh0[0], + llh0[1], + llh0[2], + ) + enu_pts[i, :] = llh_to_enu( + llhi[0], llhi[1], llhi[2], llh0[0], llh0[1], llh0[2] + ) geo_cov.append(np.cov(enu_pts.T)) @@ -553,7 +553,7 @@ def unproject_to_llh(self, points, t=None, cov=None): llh = np.array(llh).T if cov is not None: - return llh,geo_cov + return llh, geo_cov else: return llh @@ -568,25 +568,34 @@ def points_along_image_border(self, num_points=4): :rtype: numpy.ndarry with shape (3,n) """ - perimeter = 2*(self.height + self.width) - ds = num_points/float(perimeter) - xn = np.max([2,int(ds*self.width)]) - yn = np.max([2,int(ds*self.height)]) + perimeter = 2 * (self.height + self.width) + ds = num_points / float(perimeter) + xn = np.max([2, int(ds * self.width)]) + yn = np.max([2, int(ds * self.height)]) x = np.linspace(0, self.width, xn) y = np.linspace(0, self.height, yn)[1:-1] - pts = np.vstack([np.hstack([x, - np.full(len(y), self.width, - dtype=np.float64), - x[::-1], - np.zeros(len(y))]), - np.hstack([np.zeros(xn), - y, - np.full(xn, self.height, - dtype=np.float64), - y[::-1]])]) + pts = np.vstack( + [ + np.hstack( + [ + x, + np.full(len(y), self.width, dtype=np.float64), + x[::-1], + np.zeros(len(y)), + ] + ), + np.hstack( + [ + np.zeros(xn), + y, + np.full(xn, self.height, dtype=np.float64), + y[::-1], + ] + ), + ] + ) return pts - def ifov(self, t=None): """Instantaneous field of view (ifov) at the image center. @@ -603,13 +612,13 @@ def ifov(self, t=None): if t is None: t = time.time() - cx = self.width/2 - cy = self.height/2 - ray1 = self.unproject([cx,cy], t)[1] + cx = self.width / 2 + cy = self.height / 2 + ray1 = self.unproject([cx, cy], t)[1] ray1 /= np.sqrt(np.sum(ray1**2, 0)) - ray2 = self.unproject([cx,cy+1], t)[1] + ray2 = self.unproject([cx, cy + 1], t)[1] ray2 /= np.sqrt(np.sum(ray2**2, 0)) - ray3 = self.unproject([cx+1,cy], t)[1] + ray3 = self.unproject([cx + 1, cy], t)[1] ray3 /= np.sqrt(np.sum(ray3**2, 0)) ifovx = np.arccos(np.dot(ray1.ravel(), ray3.ravel())) @@ -632,53 +641,29 @@ def fov(self, t=None): if t is None: t = time.time() - cx = self.width/2 - cy = self.height/2 + cx = self.width / 2 + cy = self.height / 2 - ray1 = self.unproject([cx,0], t)[1] + ray1 = self.unproject([cx, 0], t)[1] ray1 /= np.sqrt(np.sum(ray1**2, 0)) - ray2 = self.unproject([cx,self.height], t)[1] + ray2 = self.unproject([cx, self.height], t)[1] ray2 /= np.sqrt(np.sum(ray2**2, 0)) - fov_v = np.arccos(np.dot(ray1.ravel(), ray2.ravel()))*180/np.pi + fov_v = np.arccos(np.dot(ray1.ravel(), ray2.ravel())) * 180 / np.pi - ray1 = self.unproject([0,cy], t)[1] + ray1 = self.unproject([0, cy], t)[1] ray1 /= np.sqrt(np.sum(ray1**2, 0)) ray2 = self.unproject([self.width, cy], t)[1] ray2 /= np.sqrt(np.sum(ray2**2, 0)) - fov_h = np.arccos(np.dot(ray1.ravel(), ray2.ravel()))*180/np.pi + fov_h = np.arccos(np.dot(ray1.ravel(), ray2.ravel())) * 180 / np.pi - ray1 = self.unproject([0,0], t)[1] + ray1 = self.unproject([0, 0], t)[1] ray1 /= np.sqrt(np.sum(ray1**2, 0)) - ray2 = self.unproject([self.width,self.height], t)[1] + ray2 = self.unproject([self.width, self.height], t)[1] ray2 /= np.sqrt(np.sum(ray2**2, 0)) - fov_d = np.arccos(np.dot(ray1.ravel(), ray2.ravel()))*180/np.pi + fov_d = np.arccos(np.dot(ray1.ravel(), ray2.ravel())) * 180 / np.pi return fov_h, fov_v, fov_d - def unproject_to_depth(self, points, t=None): - """See Camera.unproject_to_depth documentation. - - """ - points = self._unproject_to_depth(points, self.depth_map, t=t) - return points - - def save_depth_viz(self, fname): - depth_image = self.depth_map.copy() - v = depth_image[np.isfinite(depth_image)] - if len(v) > 0: - vmin = np.percentile(v, 1) - vmax = np.percentile(v, 99) - depth_image -= vmin - depth_image[depth_image < 0] = 0 - v = vmax - vmin - if v > 0: - depth_image /= v/255 - - depth_image = np.round(depth_image).astype(np.uint8) - - depth_image = cv2.applyColorMap(depth_image, cv2.COLORMAP_JET) - cv2.imwrite(fname, depth_image[:, :, ::-1]) - class StandardCamera(Camera): """Standard camera model. @@ -706,14 +691,15 @@ class StandardCamera(Camera): :type dist: numpy.ndarray """ - def __init__(self, width, height, K, dist, cam_pos, cam_quat, - platform_pose_provider=None): + + def __init__( + self, width, height, K, dist, cam_pos, cam_quat, platform_pose_provider=None + ): """ See additional documentation from base class above. """ - super(StandardCamera, self).__init__(width, height, - platform_pose_provider) + super(StandardCamera, self).__init__(width, height, platform_pose_provider) self._K = np.array(K, dtype=np.float64) self._dist = np.atleast_1d(dist).astype(np.float32) @@ -723,74 +709,69 @@ def __init__(self, width, height, K, dist, cam_pos, cam_quat, self._min_ray_cos = None def __str__(self): - string = ['model_type: standard\n'] + string = [f"model_type: {self.model_type}\n"] string.append(super(StandardCamera, self).__str__()) - string.append('\n') - string.append(''.join(['fx: ',repr(self._K[0,0]),'\n'])) - string.append(''.join(['fy: ',repr(self._K[1,1]),'\n'])) - string.append(''.join(['cx: ',repr(self._K[0,2]),'\n'])) - string.append(''.join(['cy: ',repr(self._K[1,2]),'\n'])) - string.append(''.join(['distortion_coefficients: ', - repr(tuple(self._dist)), - '\n'])) - string.append(''.join(['camera_quaternion: ', - repr(tuple(self._cam_quat)),'\n'])) - string.append(''.join(['camera_position: ',repr(tuple(self._cam_pos)), - '\n'])) - return ''.join(string) + string.append("\n") + string.append("".join(["fx: ", repr(self._K[0, 0]), "\n"])) + string.append("".join(["fy: ", repr(self._K[1, 1]), "\n"])) + string.append("".join(["cx: ", repr(self._K[0, 2]), "\n"])) + string.append("".join(["cy: ", repr(self._K[1, 2]), "\n"])) + string.append( + "".join(["distortion_coefficients: ", repr(tuple(self._dist)), "\n"]) + ) + string.append( + "".join(["camera_quaternion: ", repr(tuple(self._cam_quat)), "\n"]) + ) + string.append("".join(["camera_position: ", repr(tuple(self._cam_pos)), "\n"])) + return "".join(string) @classmethod def load_from_file(cls, filename, platform_pose_provider=None): - """See base class Camera documentation. - - """ - with open(filename, 'r') as f: + """See base class Camera documentation.""" + with open(filename, "r") as f: calib = yaml.safe_load(f) - assert calib['model_type'] == 'standard' + assert calib["model_type"] == "standard" # fill in CameraInfo fields - width = int(calib['image_width']) - height = int(calib['image_height']) - dist = calib['distortion_coefficients'] + width = int(calib["image_width"]) + height = int(calib["image_height"]) + dist = calib["distortion_coefficients"] - if dist == 'None': + if dist == "None": dist = np.zeros(4) dist = np.float64(dist) - fx = np.float64(calib['fx']) - fy = np.float64(calib['fy']) - cx = np.float64(calib['cx']) - cy = np.float64(calib['cy']) - K = np.array([[fx,0,cx],[0,fy,cy],[0,0,1]]) + fx = np.float64(calib["fx"]) + fy = np.float64(calib["fy"]) + cx = np.float64(calib["cx"]) + cy = np.float64(calib["cy"]) + K = np.array([[fx, 0, cx], [0, fy, cy], [0, 0, 1]]) - cam_quat = calib['camera_quaternion'] - cam_pos = calib['camera_position'] + cam_quat = calib["camera_quaternion"] + cam_pos = calib["camera_position"] - return cls(width, height, K, dist, cam_pos, cam_quat, - platform_pose_provider) + return cls(width, height, K, dist, cam_pos, cam_quat, platform_pose_provider) @classmethod def load_from_krtd(cls, filename): - """See base class Camera documentation. - - """ + """See base class Camera documentation.""" data = [] with open(filename) as f: for line in f.readlines(): - data.append(line.strip('\n')) + data.append(line.strip("\n")) - fx = float(data[0].split(' ' )[0]) - fy = float(data[1].split(' ' )[1]) - cx = float(data[0].split(' ' )[2]) - cy = float(data[1].split(' ' )[2]) + fx = float(data[0].split(" ")[0]) + fy = float(data[1].split(" ")[1]) + cx = float(data[0].split(" ")[2]) + cy = float(data[1].split(" ")[2]) R = np.zeros((3, 3)) for i in range(3): - R[i] = [float(d) for d in data[4 + i].split(' ')] + R[i] = [float(d) for d in data[4 + i].split(" ")] - tvec = [float(d) for d in data[8].split(' ')] + tvec = [float(d) for d in data[8].split(" ")] cam_pos = -np.dot(R.T, tvec).ravel() @@ -800,59 +781,80 @@ def load_from_krtd(cls, filename): width = None height = None - dist = [float(d) for d in data[10].split(' ') if len(d) > 0] + dist = [float(d) for d in data[10].split(" ") if len(d) > 0] dist = np.array(dist) - K = np.array([[fx, 0, cx], [0, fy, cy],[0, 0, 1]]) + K = np.array([[fx, 0, cx], [0, fy, cy], [0, 0, 1]]) return cls(width, height, K, dist, cam_pos, cam_quat) - def save_to_file(self, filename): - """See base class Camera documentation. - - """ - with open(filename, 'w') as f: - f.write(''.join(['# The type of camera model.\n', - 'model_type: standard\n\n', - '# Image dimensions\n'])) - - f.write(''.join(['image_width: ',to_str(self.width),'\n'])) - f.write(''.join(['image_height: ',to_str(self.height),'\n\n'])) - - f.write('# Focal length along the image\'s x-axis.\n') - f.write(''.join(['fx: ',to_str(self.K[0,0]),'\n\n'])) - - f.write('# Focal length along the image\'s y-axis.\n') - f.write(''.join(['fy: ',to_str(self.K[1,1]),'\n\n'])) - - f.write('# Principal point is located at (cx,cy).\n') - f.write(''.join(['cx: ',to_str(self.K[0,2]),'\n'])) - f.write(''.join(['cy: ',to_str(self.K[1,2]),'\n\n'])) - - f.write(''.join(['# Distortion coefficients following OpenCv\'s ', - 'convention\n'])) - - dist = self.dist - if np.all(dist == 0): - dist = 'None' + def _write_intrinsics(self, f, extra=""): + """Write the yaml fields every camera model shares: model type, image + dimensions, any ``extra`` lines, intrinsics and distortion.""" + f.write( + "".join( + [ + "# The type of camera model.\n", + f"model_type: {self.model_type}\n\n", + "# Image dimensions\n", + ] + ) + ) - f.write(''.join(['distortion_coefficients: ', - to_str(self.dist),'\n\n'])) + f.write("".join(["image_width: ", to_str(self.width), "\n"])) + f.write("".join(["image_height: ", to_str(self.height), "\n\n"])) + + f.write(extra) + + f.write("# Focal length along the image's x-axis.\n") + f.write("".join(["fx: ", to_str(self.K[0, 0]), "\n\n"])) + + f.write("# Focal length along the image's y-axis.\n") + f.write("".join(["fy: ", to_str(self.K[1, 1]), "\n\n"])) + + f.write("# Principal point is located at (cx,cy).\n") + f.write("".join(["cx: ", to_str(self.K[0, 2]), "\n"])) + f.write("".join(["cy: ", to_str(self.K[1, 2]), "\n\n"])) + + f.write("# Distortion coefficients following OpenCv's convention\n") + f.write("".join(["distortion_coefficients: ", to_str(self.dist), "\n\n"])) + + def _write_pose(self, f): + """Write the camera's orientation and position on the platform.""" + f.write( + "".join( + [ + "# Quaternion (x, y, z, w) specifying the ", + "orientation of the camera relative to\n# the ", + "platform coordinate system. The quaternion ", + "represents a coordinate\n# system rotation that ", + "takes the platform coordinate system and ", + "rotates it\n# into the camera coordinate ", + "system.\ncamera_quaternion: ", + to_str(self.cam_quat), + "\n\n", + ] + ) + ) - f.write(''.join(['# Quaternion (x, y, z, w) specifying the ', - 'orientation of the camera relative to\n# the ', - 'platform coordinate system. The quaternion ', - 'represents a coordinate\n# system rotation that ', - 'takes the platform coordinate system and ', - 'rotates it\n# into the camera coordinate ', - 'system.\ncamera_quaternion: ', - to_str(self.cam_quat),'\n\n'])) + f.write( + "".join( + [ + "# Position of the camera's center of ", + "projection within the navigation\n# coordinate ", + "system.\n", + "camera_position: ", + to_str(self.cam_pos), + "\n\n", + ] + ) + ) - f.write(''.join(['# Position of the camera\'s center of ', - 'projection within the navigation\n# coordinate ', - 'system.\n', - 'camera_position: ', to_str(self.cam_pos), - '\n\n'])) + def save_to_file(self, filename): + """See base class Camera documentation.""" + with open(filename, "w") as f: + self._write_intrinsics(f) + self._write_pose(f) @property def K(self): @@ -860,77 +862,74 @@ def K(self): @property def K_no_skew(self): - """Returns a compact version of K assuming no skew. - - """ + """Returns a compact version of K assuming no skew.""" K = self.K - return np.array([K[0,0],K[1,1],K[0,2],K[1,2]]) + return np.array([K[0, 0], K[1, 1], K[0, 2], K[1, 2]]) @K_no_skew.setter def K_no_skew(self, value): - """fx, fy, cx, cy - """ - K = np.zeros((3,3), dtype=np.float64) - K[0,0] = value[0] - K[1,1] = value[1] - K[0,2] = value[2] - K[1,2] = value[3] + """fx, fy, cx, cy""" + K = np.zeros((3, 3), dtype=np.float64) + K[0, 0] = value[0] + K[1, 1] = value[1] + K[0, 2] = value[2] + K[1, 2] = value[3] self._K = K self._min_ray_cos = None @property def focal_length(self): - return self._K[0,0] + return self._K[0, 0] @focal_length.setter def focal_length(self, value): - self._K[0,0] = value - self._K[1,1] = value + self._K[0, 0] = value + self._K[1, 1] = value self._min_ray_cos = None @property def fx(self): - return self._K[0,0] + return self._K[0, 0] @property def fy(self): - return self._K[1,1] + return self._K[1, 1] @fx.setter def fx(self, value): - self._K[0,0] = value + self._K[0, 0] = value self._min_ray_cos = None @fy.setter def fy(self, value): - self._K[1,1] = value + self._K[1, 1] = value self._min_ray_cos = None @property def cx(self): - return self._K[0,2] + return self._K[0, 2] @property def cy(self): - return self._K[1,2] + return self._K[1, 2] @cx.setter def cx(self, value): - self._K[0,2] = value + self._K[0, 2] = value self._min_ray_cos = None @cy.setter def cy(self, value): - self._K[1,2] = value + self._K[1, 2] = value self._min_ray_cos = None @property def aspect_ratio(self): - return self._K[0,0]/self._K[1,1] + return self._K[0, 0] / self._K[1, 1] @aspect_ratio.setter def aspect_ratio(self, value): - self._K[1,1] = self._K[0,0]*value + self._K[1, 1] = self._K[0, 0] * value @property def dist(self): @@ -948,6 +947,10 @@ def dist(self, value): def cam_pos(self): return self._cam_pos + @cam_pos.setter + def cam_pos(self, value): + self._cam_pos = value + @property def cam_quat(self): return self._cam_quat @@ -977,8 +980,8 @@ def min_ray_cos(self): ray0 = self.unproject(center, t, normalize_ray_dir=True)[1].ravel() w, h = self.width, self.height self._min_ray_cos = 1 - for x,y in [[0,0],[w,0],[w,h],[0,h]]: - ray1 = self.unproject([x, y], t, normalize_ray_dir=True)[1] + for x, y in [[0, 0], [w, 0], [w, h], [0, h]]: + ray1 = self.unproject([x, y], t, normalize_ray_dir=True)[1] ray1 = ray1.ravel() ray_cosi = np.dot(ray0, ray1) self._min_ray_cos = np.minimum(self._min_ray_cos, ray_cosi) @@ -988,8 +991,7 @@ def min_ray_cos(self): return self._min_ray_cos def update_intrinsics(self, K=None, cam_quat=None, dist=None): - """ - """ + """ """ if K is not None: self._K = K.astype(np.float64) if cam_quat is not None: @@ -1022,9 +1024,7 @@ def get_camera_pose(self, t=None): return np.dot(p_cam, p_ins)[:3] def project(self, points, t=None): - """See Camera.project documentation. - - """ + """See Camera.project documentation.""" points = np.array(points, dtype=np.float64) if points.ndim == 1: points = np.atleast_2d(points).T @@ -1033,53 +1033,50 @@ def project(self, points, t=None): t = time.time() pose_mat = self.get_camera_pose(t) - pose_mat = np.vstack((pose_mat, np.array([0,0,0,1]))) + pose_mat = np.vstack((pose_mat, np.array([0, 0, 0, 1]))) # Project rays into camera coordinate system. - rvec = cv2.Rodrigues(pose_mat[:3,:3])[0].ravel() + rvec = cv2.Rodrigues(pose_mat[:3, :3])[0].ravel() tvec = pose_mat[:3, 3] - im_pts = cv2.projectPoints(points.T, rvec, tvec, self._K, - self._dist)[0] + im_pts = cv2.projectPoints(points.T, rvec, tvec, self._K, self._dist)[0] im_pts = np.squeeze(im_pts, 1).T # Make homogeneous points = np.vstack([points, np.ones(points.shape[1])]) points = np.dot(pose_mat, points) - #points /= np.sqrt(np.sum(points**2, 0)) + # points /= np.sqrt(np.sum(points**2, 0)) points /= points[3, :] # Add the 1e-8 to avoid "falling off the focal plane" due to rounding # error. # ind = points[2] <= self.min_ray_cos - #im_pts[:, ind] = np.nan + # im_pts[:, ind] = np.nan return im_pts def unproject(self, points, t=None, normalize_ray_dir=True): - """See Camera.unproject documentation. - - """ + """See Camera.unproject documentation.""" points = np.array(points, dtype=np.float64) if points.ndim == 1: points = np.atleast_2d(points).T - points = np.reshape(points, (2,-1)) + points = np.reshape(points, (2, -1)) if t is None: t = time.time() ins_pos, ins_quat = self.platform_pose_provider.pose(t) - #print('ins_pos', ins_pos) - #print('ins_quat', ins_quat) # Unproject rays into the camera coordinate system. - ray_dir = np.ones((3,points.shape[1]), dtype=points.dtype) - ray_dir0 = cv2.undistortPoints(np.expand_dims(points.T, 1), - self._K, self._dist, R=None) + ray_dir = np.ones((3, points.shape[1]), dtype=points.dtype) + ray_dir0 = cv2.undistortPoints( + np.expand_dims(points.T, 1), self._K, self._dist, R=None + ) ray_dir[:2] = np.squeeze(ray_dir0, 1).T + R_cam_to_world = Rotation.from_quat(self._cam_quat).as_matrix() # Rotate rays into the navigation coordinate system. - ray_dir = np.dot(quaternion_matrix(self._cam_quat)[:3,:3], ray_dir) + ray_dir = np.dot(R_cam_to_world, ray_dir) # Translate ray positions into their navigation coordinate system # definition. @@ -1089,7 +1086,7 @@ def unproject(self, points, t=None, normalize_ray_dir=True): ray_pos[2] = self._cam_pos[2] # Rotate and translate rays into the world coordinate system. - R_ins_to_world = quaternion_matrix(ins_quat)[:3,:3] + R_ins_to_world = Rotation.from_quat(ins_quat).as_matrix() ray_dir = np.dot(R_ins_to_world, ray_dir) ray_pos = np.dot(R_ins_to_world, ray_pos) + np.atleast_2d(ins_pos).T @@ -1126,108 +1123,84 @@ class RollingShutterCamera(StandardCamera): :type dist: numpy.ndarray """ - def __init__(self, width, height, K, dist, cam_pos, cam_quat, - shutter_roll_time, platform_pose_provider=None): + + model_type = "rolling_shutter" + + def __init__( + self, + width, + height, + K, + dist, + cam_pos, + cam_quat, + shutter_roll_time, + platform_pose_provider=None, + ): """ See additional documentation from base class above. """ - super(RollingShutterCamera, self).__init__(width, height, K, dist, - cam_pos, cam_quat, - platform_pose_provider) + super(RollingShutterCamera, self).__init__( + width, height, K, dist, cam_pos, cam_quat, platform_pose_provider + ) self.shutter_roll_time = shutter_roll_time def __str__(self): - string = ['model_type: rolling_shutter\n'] - string.append(super(RollingShutterCamera, self).__str__()) - string.append('shutter_roll_time: %s\n' %self.shutter_roll_time) - return ''.join(string) + string = [super(RollingShutterCamera, self).__str__()] + string.append("shutter_roll_time: %s\n" % self.shutter_roll_time) + return "".join(string) @classmethod def load_from_file(cls, filename, platform_pose_provider=None): - """See base class Camera documentation. - - """ - with open(filename, 'r') as f: + """See base class Camera documentation.""" + with open(filename, "r") as f: calib = yaml.safe_load(f) - assert calib['model_type'] == 'rolling_shutter' + assert calib["model_type"] == "rolling_shutter" # fill in CameraInfo fields - width = calib['image_width'] - height = calib['image_height'] - shutter_roll_time = calib['shutter_roll_time'] - dist = calib['distortion_coefficients'] + width = calib["image_width"] + height = calib["image_height"] + shutter_roll_time = calib["shutter_roll_time"] + dist = calib["distortion_coefficients"] - if dist == 'None': + if dist == "None": dist = np.zeros(4) - fx = calib['fx'] - fy = calib['fy'] - cx = calib['cx'] - cy = calib['cy'] - K = np.array([[fx,0,cx],[0,fy,cy],[0,0,1]]) - - cam_quat = calib['camera_quaternion'] - cam_pos = calib['camera_position'] - - return cls(width, height, K, dist, cam_pos, cam_quat, - shutter_roll_time, platform_pose_provider) + fx = calib["fx"] + fy = calib["fy"] + cx = calib["cx"] + cy = calib["cy"] + K = np.array([[fx, 0, cx], [0, fy, cy], [0, 0, 1]]) + + cam_quat = calib["camera_quaternion"] + cam_pos = calib["camera_position"] + + return cls( + width, + height, + K, + dist, + cam_pos, + cam_quat, + shutter_roll_time, + platform_pose_provider, + ) def save_to_file(self, filename): - """See base class Camera documentation. - - """ - with open(filename, 'w') as f: - f.write(''.join(['# The type of camera model.\n', - 'model_type: rolling_shutter\n\n', - '# Image dimensions\n'])) - - f.write(''.join(['image_width: ',to_str(self.width),'\n'])) - f.write(''.join(['image_height: ',to_str(self.height),'\n\n'])) - - f.write(''.join(['shutter_roll_time: ', - to_str(self.shutter_roll_time),'\n\n'])) - - f.write('# Focal length along the image\'s x-axis.\n') - f.write(''.join(['fx: ',to_str(self.K[0,0]),'\n\n'])) - - f.write('# Focal length along the image\'s y-axis.\n') - f.write(''.join(['fy: ',to_str(self.K[1,1]),'\n\n'])) - - f.write('# Principal point is located at (cx,cy).\n') - f.write(''.join(['cx: ',to_str(self.K[0,2]),'\n'])) - f.write(''.join(['cy: ',to_str(self.K[1,2]),'\n\n'])) - - f.write(''.join(['# Distortion coefficients following OpenCv\'s ', - 'convention\n'])) - - dist = self.dist - if np.all(dist == 0): - dist = 'None' - - f.write(''.join(['distortion_coefficients: ', - to_str(self.dist),'\n\n'])) - - f.write(''.join(['# Quaternion (x, y, z, w) specifying the ', - 'orientation of the camera relative to\n# the ', - 'platform coordinate system. The quaternion ', - 'represents a coordinate\n# system rotation that ', - 'takes the platform coordinate system and ', - 'rotates it\n# into the camera coordinate ', - 'system.\ncamera_quaternion: ', - to_str(self.cam_quat),'\n\n'])) - - f.write(''.join(['# Position of the camera\'s center of ', - 'projection within the navigation\n# coordinate ', - 'system.\n', - 'camera_position: ',to_str(self.cam_pos), - '\n\n'])) + """See base class Camera documentation.""" + with open(filename, "w") as f: + self._write_intrinsics( + f, + "".join( + ["shutter_roll_time: ", to_str(self.shutter_roll_time), "\n\n"] + ), + ) + self._write_pose(f) def project(self, points, t=None): - """See Camera.project documentation. - - """ + """See Camera.project documentation.""" # The challenge projecting into a rolling shutter camera is that every # row of the image is exposed at a different time. So, if you assume a # particular time to evaluate the pose at and then project into the @@ -1238,7 +1211,7 @@ def project(self, points, t=None): # We start by projecting assuming all points are at the time associated # with the center of the field of view. - im_pts = proj_fun(points, t + 0.5*self.shutter_roll_time) + im_pts = proj_fun(points, t + 0.5 * self.shutter_roll_time) if False: # Slower but more accurate. @@ -1255,27 +1228,29 @@ def project(self, points, t=None): if ind[i]: # The fraction of the rolling shutter time this y # coordinate has accumulated. - alpha = np.clip(im_pts[1, i]/self.height, 0, 1) - t_ = t + alpha*self.shutter_roll_time - im_pt_ = proj_fun(points[:, i:i+1], t_) - d = sqrt(np.sum((im_pt_ - im_pts[:, i:i+1])**2)) + alpha = np.clip(im_pts[1, i] / self.height, 0, 1) + t_ = t + alpha * self.shutter_roll_time + im_pt_ = proj_fun(points[:, i : i + 1], t_) + d = sqrt(np.sum((im_pt_ - im_pts[:, i : i + 1]) ** 2)) if d > 0.01: cont = True else: ind[i] = False - im_pts[:, i:i+1] = im_pt_ + im_pts[:, i : i + 1] = im_pt_ if not cont: break else: N = 10 alphas = np.linspace(0, 1, N) - im_pts_list = [proj_fun(points, t + alpha*self.shutter_roll_time).T - for alpha in alphas] + im_pts_list = [ + proj_fun(points, t + alpha * self.shutter_roll_time).T + for alpha in alphas + ] im_pts_list = np.array(im_pts_list).T - alphas2 = np.clip(im_pts_list[1]/self.height, 0, 1) + alphas2 = np.clip(im_pts_list[1] / self.height, 0, 1) alpha_err = alphas2 - alphas # We want to interpolate to the zero-valued alpha error. @@ -1295,32 +1270,40 @@ def project(self, points, t=None): delta = alpha_err1 - alpha_err2 w = np.ones(len(alpha_err1)) ind = delta != 0 - w[ind] = (alpha_err1[ind])/delta[ind] + w[ind] = (alpha_err1[ind]) / delta[ind] - im_pts1 = np.hstack([np.take_along_axis(im_pts_list[0], ind1, axis=1), - np.take_along_axis(im_pts_list[1], ind1, axis=1)]).T + im_pts1 = np.hstack( + [ + np.take_along_axis(im_pts_list[0], ind1, axis=1), + np.take_along_axis(im_pts_list[1], ind1, axis=1), + ] + ).T - im_pts2 = np.hstack([np.take_along_axis(im_pts_list[0], ind2, axis=1), - np.take_along_axis(im_pts_list[1], ind2, axis=1)]).T + im_pts2 = np.hstack( + [ + np.take_along_axis(im_pts_list[0], ind2, axis=1), + np.take_along_axis(im_pts_list[1], ind2, axis=1), + ] + ).T - im_pts = w*im_pts2 + (1-w)*im_pts1 + im_pts = w * im_pts2 + (1 - w) * im_pts1 return im_pts def unproject(self, points, t, normalize_ray_dir=True): - """See Camera.unproject documentation. - - """ + """See Camera.unproject documentation.""" points = np.array(points, dtype=np.float64) if points.ndim == 1: points = np.atleast_2d(points).T - points = np.reshape(points, (2,-1)) + points = np.reshape(points, (2, -1)) - alphas = np.clip(points[1]/self.height, 0, 1) - ts_ = t + (alphas*self.shutter_roll_time).astype(np.float64) + alphas = np.clip(points[1] / self.height, 0, 1) + ts_ = t + (alphas * self.shutter_roll_time).astype(np.float64) - ret = [super(RollingShutterCamera, self).unproject(points[:, i:i+1], ts_[i]) - for i in range(len(ts_))] + ret = [ + super(RollingShutterCamera, self).unproject(points[:, i : i + 1], ts_[i]) + for i in range(len(ts_)) + ] ray_pos = np.hstack([ret_[0] for ret_ in ret]) ray_dir = np.hstack([ret_[1] for ret_ in ret]) @@ -1328,125 +1311,105 @@ def unproject(self, points, t, normalize_ray_dir=True): class DepthCamera(StandardCamera): - """Camera with depth map. - - """ - def __init__(self, width, height, K, dist, cam_pos, cam_quat, depth_map, - platform_pose_provider=None): + """Camera with depth map.""" + + model_type = "depth" + + def __init__( + self, + width, + height, + K, + dist, + cam_pos, + cam_quat, + depth_map, + platform_pose_provider=None, + ): """ See additional documentation from base class above. """ - super(DepthCamera, self).__init__(width=width, height=height, K=K, - dist=dist, cam_pos=cam_pos, - cam_quat=cam_quat, - platform_pose_provider=platform_pose_provider) + super(DepthCamera, self).__init__( + width=width, + height=height, + K=K, + dist=dist, + cam_pos=cam_pos, + cam_quat=cam_quat, + platform_pose_provider=platform_pose_provider, + ) self._depth_map = depth_map @classmethod def load_from_file(cls, filename, platform_pose_provider=None): - """See base class Camera documentation. - - """ - with open(filename, 'r') as f: + """See base class Camera documentation.""" + with open(filename, "r") as f: calib = yaml.safe_load(f) - assert calib['model_type'] == 'depth' + assert calib["model_type"] == "depth" # fill in CameraInfo fields - width = calib['image_width'] - height = calib['image_height'] - dist = calib['distortion_coefficients'] + width = calib["image_width"] + height = calib["image_height"] + dist = calib["distortion_coefficients"] - if dist == 'None': + if dist == "None": dist = np.zeros(4) - fx = calib['fx'] - fy = calib['fy'] - cx = calib['cx'] - cy = calib['cy'] - K = np.array([[fx,0,cx],[0,fy,cy],[0,0,1]]) - - cam_quat = calib['camera_quaternion'] - cam_pos = calib['camera_position'] + fx = calib["fx"] + fy = calib["fy"] + cx = calib["cx"] + cy = calib["cy"] + K = np.array([[fx, 0, cx], [0, fy, cy], [0, 0, 1]]) - return cls(width, height, K, dist, cam_pos, cam_quat, - platform_pose_provider) + cam_quat = calib["camera_quaternion"] + cam_pos = calib["camera_position"] - def save_to_file(self, filename, save_depth_viz=True): - """See base class Camera documentation. - - """ - with open(filename, 'w') as f: - f.write(''.join(['# The type of camera model.\n', - 'model_type: depth\n\n', - '# Image dimensions\n'])) - - f.write(''.join(['image_width: ',to_str(self.width),'\n'])) - f.write(''.join(['image_height: ',to_str(self.height),'\n\n'])) - - f.write('# Focal length along the image\'s x-axis.\n') - f.write(''.join(['fx: ',to_str(self.K[0,0]),'\n\n'])) - - f.write('# Focal length along the image\'s y-axis.\n') - f.write(''.join(['fy: ',to_str(self.K[1,1]),'\n\n'])) - - f.write('# Principal point is located at (cx,cy).\n') - f.write(''.join(['cx: ',to_str(self.K[0,2]),'\n'])) - f.write(''.join(['cy: ',to_str(self.K[1,2]),'\n\n'])) - - f.write(''.join(['# Distortion coefficients following OpenCv\'s ', - 'convention\n'])) - - dist = self.dist - if np.all(dist == 0): - dist = 'None' - - f.write(''.join(['distortion_coefficients: ', - to_str(self.dist),'\n\n'])) + depth_map_fname = "%s_depth_map.tif" % os.path.splitext(filename)[0] + try: + depth_map = np.asarray(PIL.Image.open(depth_map_fname)) + except OSError: + depth_map = None - f.write(''.join(['# Quaternion (x, y, z, w) specifying the ', - 'orientation of the camera relative to\n# the ', - 'navigation coordinate system. The quaternion ', - 'represents a coordinate\n# system rotation that ', - 'takes the navigation coordinate system and ', - 'rotates it\n# into the camera coordinate ', - 'system.\n camera_quaternion: ', - to_str(self.cam_quat),'\n\n'])) + return cls( + width, height, K, dist, cam_pos, cam_quat, depth_map, platform_pose_provider + ) - f.write(''.join(['# Position of the camera\'s center of ', - 'projection within the navigation\n# coordinate ', - 'system.\n', - 'camera_position: ',to_str(self.cam_pos), - '\n\n'])) + def save_to_file(self, filename, save_depth_viz=True): + """See base class Camera documentation.""" + with open(filename, "w") as f: + self._write_intrinsics(f) + self._write_pose(f) if self.depth_map is not None: - im = PIL.Image.fromarray(self.depth_map.astype(np.float32), - mode='F') # float32 - depth_map_fname = '%s_depth_map.tif' % os.path.splitext(filename)[0] + im = PIL.Image.fromarray( + self.depth_map.astype(np.float32), mode="F" + ) # float32 + depth_map_fname = "%s_depth_map.tif" % os.path.splitext(filename)[0] im.save(depth_map_fname) if save_depth_viz: - depth_viz_fname = ('%s/depth_vizualization.png' % - os.path.split(filename)[0]) + depth_viz_fname = os.path.join( + os.path.dirname(filename), "depth_vizualization.png" + ) self.save_depth_viz(depth_viz_fname) def __str__(self): - string = ['model_type: depth\n'] - string.append(super(DepthCamera, self).__str__()) - string.append('\n') - string.append(''.join(['fx: ',repr(self._K[0,0]),'\n'])) - string.append(''.join(['fy: ',repr(self._K[1,1]),'\n'])) - string.append(''.join(['cx: ',repr(self._K[0,2]),'\n'])) - string.append(''.join(['cy: ',repr(self._K[1,2]),'\n'])) - string.append(''.join(['distortion_coefficients: ', - repr(tuple(self._dist)), - '\n'])) - string.append(''.join(['camera_quaternion: ', - repr(tuple(self._cam_quat)),'\n'])) - string.append(''.join(['camera_position: ',repr(tuple(self._cam_pos)), - '\n'])) - return ''.join(string) + string = [super(DepthCamera, self).__str__()] + string.append("\n") + string.append("".join(["fx: ", repr(self._K[0, 0]), "\n"])) + string.append("".join(["fy: ", repr(self._K[1, 1]), "\n"])) + string.append("".join(["cx: ", repr(self._K[0, 2]), "\n"])) + string.append("".join(["cy: ", repr(self._K[1, 2]), "\n"])) + string.append( + "".join(["distortion_coefficients: ", repr(tuple(self._dist)), "\n"]) + ) + string.append( + "".join(["camera_quaternion: ", repr(tuple(self._cam_quat)), "\n"]) + ) + string.append("".join(["camera_position: ", repr(tuple(self._cam_pos)), "\n"])) + return "".join(string) def _unproject_to_depth(self, points, depth_map, t=None): """Unproject image points into the world at a particular time. @@ -1468,11 +1431,11 @@ def _unproject_to_depth(self, points, depth_map, t=None): """ points = np.atleast_2d(points) - points = np.reshape(points, (2,-1)) + points = np.reshape(points, (2, -1)) ray_pos, ray_dir = self.unproject(points, t=t, normalize_ray_dir=False) for i in range(points.shape[1]): - x,y = points[:,i] + x, y = points[:, i] # Get ray distance traveled until intersection. Therefore, we need # to evaluate the depth map at x,y. We need to convert from image # coordinates (i.e., upper-left corner of upper-left pixel is 0,0) @@ -1495,14 +1458,37 @@ def _unproject_to_depth(self, points, depth_map, t=None): if ix < 0 or iy < 0 or ix >= self.width or iy >= self.height: print(x == self.width) print(y == self.height) - raise ValueError('Coordinates (%0.1f,%0.f) are outside the ' - '%ix%i image' % - (x,y,self.width,self.height)) + raise ValueError( + "Coordinates (%0.1f,%0.f) are outside the " + "%ix%i image" % (x, y, self.width, self.height) + ) - ray_pos[:,i] += ray_dir[:,i]*depth_map[iy,ix] + ray_pos[:, i] += ray_dir[:, i] * depth_map[iy, ix] return ray_pos + def unproject_to_depth(self, points, t=None): + """See Camera.unproject_to_depth documentation.""" + points = self._unproject_to_depth(points, self.depth_map, t=t) + return points + + def save_depth_viz(self, fname): + depth_image = self.depth_map.copy() + v = depth_image[np.isfinite(depth_image)] + if len(v) > 0: + vmin = np.percentile(v, 1) + vmax = np.percentile(v, 99) + depth_image -= vmin + depth_image[depth_image < 0] = 0 + v = vmax - vmin + if v > 0: + depth_image /= v / 255 + + depth_image = np.round(depth_image).astype(np.uint8) + + depth_image = cv2.applyColorMap(depth_image, cv2.COLORMAP_JET) + cv2.imwrite(fname, depth_image[:, :, ::-1]) + class GeoStaticCamera(DepthCamera): """Stationary camera with a fixed pose at some geo-fixed location. @@ -1510,8 +1496,12 @@ class GeoStaticCamera(DepthCamera): width, height, K, dist, lat, lon, altitude, cam_quat """ - def __init__(self, width, height, K, dist, depth_map, latitude, longitude, - altitude, R): + + model_type = "static" + + def __init__( + self, width, height, K, dist, depth_map, latitude, longitude, altitude, R + ): """ See additional documentation from base class above. @@ -1527,140 +1517,126 @@ def __init__(self, width, height, K, dist, depth_map, latitude, longitude, """ R = np.array(R) R /= np.linalg.det(R) - cam_pos = np.array([0,0,0]) - cam_quat = np.array([0,0,0,1]) + cam_pos = np.array([0, 0, 0]) + cam_quat = np.array([0, 0, 0, 1]) # Quaternion for level system (z down) with x-axis pointing north. - enu_quat = np.array([1/np.sqrt(2),1/np.sqrt(2),0,0]) - - platform_pose_provider = PlatformPoseFixed(pos=np.array([0,0,0]), - quat=enu_quat, - lat0=latitude, lon0=longitude, - h0=altitude) - - super(GeoStaticCamera, self).__init__(width=width, height=height, K=K, - dist=dist, cam_pos=cam_pos, - cam_quat=cam_quat, - depth_map=depth_map, - platform_pose_provider=platform_pose_provider) + enu_quat = np.array([1 / np.sqrt(2), 1 / np.sqrt(2), 0, 0]) + + platform_pose_provider = PlatformPoseFixed( + pos=np.array([0, 0, 0]), + quat=enu_quat, + lat0=latitude, + lon0=longitude, + h0=altitude, + ) + + super(GeoStaticCamera, self).__init__( + width=width, + height=height, + K=K, + dist=dist, + cam_pos=cam_pos, + cam_quat=cam_quat, + depth_map=depth_map, + platform_pose_provider=platform_pose_provider, + ) self._R = R self._depth_map = depth_map # The local ENU coordinate system is located at the camera. - self._tvec = np.array([[0],[0],[0]], dtype=np.float64) - self._camera_pose = np.hstack([R,self._tvec]) + self._tvec = np.array([[0], [0], [0]], dtype=np.float64) + self._camera_pose = np.hstack([R, self._tvec]) def __str__(self): - string = ['model_type: static\n'] - string.append(super(GeoStaticCamera, self).__str__()) - string.append('\n') - string.append(''.join(['fx: ',repr(self._K[0,0]),'\n'])) - string.append(''.join(['fy: ',repr(self._K[1,1]),'\n'])) - string.append(''.join(['cx: ',repr(self._K[0,2]),'\n'])) - string.append(''.join(['cy: ',repr(self._K[1,2]),'\n'])) - string.append(''.join(['distortion_coefficients: ', - repr(tuple(self._dist)), - '\n'])) - string.append(''.join(['latitude: %0.8f' % self.latitude, - '\n'])) - string.append(''.join(['longitude: %0.8f' % self.longitude, - '\n'])) - string.append(''.join(['altitude: %0.8f' % self.altitude, - '\n'])) - string.append(''.join(['R: ', - repr(tuple(self.R)),'\n'])) - return ''.join(string) + string = [super(GeoStaticCamera, self).__str__()] + string.append("\n") + string.append("".join(["fx: ", repr(self._K[0, 0]), "\n"])) + string.append("".join(["fy: ", repr(self._K[1, 1]), "\n"])) + string.append("".join(["cx: ", repr(self._K[0, 2]), "\n"])) + string.append("".join(["cy: ", repr(self._K[1, 2]), "\n"])) + string.append( + "".join(["distortion_coefficients: ", repr(tuple(self._dist)), "\n"]) + ) + string.append("".join(["latitude: %0.8f" % self.latitude, "\n"])) + string.append("".join(["longitude: %0.8f" % self.longitude, "\n"])) + string.append("".join(["altitude: %0.8f" % self.altitude, "\n"])) + string.append("".join(["R: ", repr(tuple(self.R)), "\n"])) + return "".join(string) @classmethod def load_from_file(cls, filename, platform_pose_provider=None): - """See base class Camera documentation. - - """ - with open(filename, 'r') as f: + """See base class Camera documentation.""" + with open(filename, "r") as f: calib = yaml.safe_load(f) - assert calib['model_type'] == 'static' + assert calib["model_type"] == "static" # fill in CameraInfo fields - width = calib['image_width'] - height = calib['image_height'] - dist = np.array(calib['distortion_coefficients'], dtype=np.float64) + width = calib["image_width"] + height = calib["image_height"] + dist = np.array(calib["distortion_coefficients"], dtype=np.float64) - if isinstance(dist, str) and dist == 'None': + if isinstance(dist, str) and dist == "None": dist = np.zeros(4, dtype=np.float64) - fx = calib['fx'] - fy = calib['fy'] - cx = calib['cx'] - cy = calib['cy'] - K = np.array([[fx,0,cx],[0,fy,cy],[0,0,1]]) - R = np.reshape(np.array(calib['R']), (3,3)) - latitude = calib['latitude'] - longitude = calib['longitude'] - altitude = calib['altitude'] - - depth_map_fname = '%s_depth_map.tif' % os.path.splitext(filename)[0] + fx = calib["fx"] + fy = calib["fy"] + cx = calib["cx"] + cy = calib["cy"] + K = np.array([[fx, 0, cx], [0, fy, cy], [0, 0, 1]]) + R = np.reshape(np.array(calib["R"]), (3, 3)) + latitude = calib["latitude"] + longitude = calib["longitude"] + altitude = calib["altitude"] + + depth_map_fname = "%s_depth_map.tif" % os.path.splitext(filename)[0] try: depth_map = np.asarray(PIL.Image.open(depth_map_fname)) except OSError: depth_map = None - return cls(width, height, K, dist, depth_map, latitude, longitude, - altitude, R) + return cls(width, height, K, dist, depth_map, latitude, longitude, altitude, R) def save_to_file(self, filename): - """See base class Camera documentation. - - """ - with open(filename, 'w') as f: - f.write(''.join(['# The type of camera model.\n', - 'model_type: static\n\n', - '# Image dimensions\n'])) - - f.write(''.join(['image_width: ',to_str(self.width),'\n'])) - f.write(''.join(['image_height: ',to_str(self.height),'\n\n'])) - - f.write('# Focal length along the image\'s x-axis.\n') - f.write(''.join(['fx: ',to_str(self._K[0,0]),'\n\n'])) - - f.write('# Focal length along the image\'s y-axis.\n') - f.write(''.join(['fy: ',to_str(self._K[1,1]),'\n\n'])) - - f.write('# Principal point is located at (cx,cy).\n') - f.write(''.join(['cx: ',to_str(self._K[0,2]),'\n'])) - f.write(''.join(['cy: ',to_str(self._K[1,2]),'\n\n'])) - - f.write(''.join(['# Distortion coefficients following OpenCv\'s ', - 'convention\n'])) - - dist = self._dist - if np.all(dist == 0): - dist = 'None' - - f.write(''.join(['distortion_coefficients: ', - to_str(self._dist),'\n\n'])) - - f.write(''.join(['# Rotation matrix mapping vectors defined in an ' - 'east/north/up coordinate system\n# centered at ' - 'the camera into vectors defined in the camera' - 'coordinate system.\n', - 'R: [%0.10f, %0.10f, %0.10f,\n' - ' %0.10f, %0.10f, %0.10f,\n' - ' %0.10f, %0.10f, %0.10f]' % - tuple(self.R.ravel()), '\n\n'])) - - f.write(''.join(['# Location of the camera\'s center of ' - 'projection. Latitude and longitude are in\n# ' - 'degrees, and altitude is meters above the WGS84 ' - 'ellipsoid.\n', - 'latitude: %0.10f\n' % self.latitude, - 'longitude: %0.10f\n' % self.longitude, - 'altitude: %0.10f' % self.altitude,'\n\n'])) + """See base class Camera documentation.""" + with open(filename, "w") as f: + self._write_intrinsics(f) + + f.write( + "".join( + [ + "# Rotation matrix mapping vectors defined in an " + "east/north/up coordinate system\n# centered at " + "the camera into vectors defined in the camera" + "coordinate system.\n", + "R: [%0.10f, %0.10f, %0.10f,\n" + " %0.10f, %0.10f, %0.10f,\n" + " %0.10f, %0.10f, %0.10f]" % tuple(self.R.ravel()), + "\n\n", + ] + ) + ) + + f.write( + "".join( + [ + "# Location of the camera's center of " + "projection. Latitude and longitude are in\n# " + "degrees, and altitude is meters above the WGS84 " + "ellipsoid.\n", + "latitude: %0.10f\n" % self.latitude, + "longitude: %0.10f\n" % self.longitude, + "altitude: %0.10f" % self.altitude, + "\n\n", + ] + ) + ) if self.depth_map is not None: - im = PIL.Image.fromarray(self.depth_map, mode='F') # float32 - depth_map_fname = '%s_depth_map.tif' % os.path.splitext(filename)[0] + im = PIL.Image.fromarray(self.depth_map, mode="F") # float32 + depth_map_fname = "%s_depth_map.tif" % os.path.splitext(filename)[0] im.save(depth_map_fname) @property @@ -1669,7 +1645,7 @@ def R(self): @R.setter def R(self, value): - self._R /= value/np.linalg.det(value) + self._R /= value / np.linalg.det(value) self._rvec = cv2.Rodrigues(self.R)[0].ravel() @property @@ -1716,8 +1692,9 @@ def project(self, points, t=None): points = np.atleast_2d(points).T # Project rays into camera coordinate system. - im_pts = cv2.projectPoints(points.T, self._rvec, self._tvec, self.K, - self.dist)[0] + im_pts = cv2.projectPoints(points.T, self._rvec, self._tvec, self.K, self.dist)[ + 0 + ] return np.squeeze(im_pts, 1).T def unproject(self, points, t=None, normalize_ray_dir=True): @@ -1734,12 +1711,13 @@ def unproject(self, points, t=None, normalize_ray_dir=True): points = np.array(points, dtype=np.float64) if points.ndim == 1: points = np.atleast_2d(points).T - points = np.reshape(points, (2,-1)) + points = np.reshape(points, (2, -1)) # Unproject rays into the camera coordinate system. - ray_dir = np.ones((3,points.shape[1]), dtype=points.dtype) - ray_dir0 = cv2.undistortPoints(np.expand_dims(points.T, 1), - self.K, self.dist, R=None) + ray_dir = np.ones((3, points.shape[1]), dtype=points.dtype) + ray_dir0 = cv2.undistortPoints( + np.expand_dims(points.T, 1), self.K, self.dist, R=None + ) ray_dir[:2] = np.squeeze(ray_dir0, 1).T # Rotate rays into the local east/north/up coordinate system. @@ -1759,10 +1737,12 @@ class MapCamera(Camera): This object is primarily built around GDAL. """ + def __init__(self, base_layer): - super(MapCamera, self).__init__(width=base_layer.res_x, - height=base_layer.res_y) + super(MapCamera, self).__init__(width=base_layer.res_x, height=base_layer.res_y) self.base_layer = base_layer def project(self, points, t=None): - return np.array([self.base_layer.meters_to_raster(point) for point in points.T]).T + return np.array( + [self.base_layer.meters_to_raster(point) for point in points.T] + ).T diff --git a/kamera/colmap_processing/geo_conversions.py b/kamera/colmap_processing/geo_conversions.py index 8f63b198..052f618b 100644 --- a/kamera/colmap_processing/geo_conversions.py +++ b/kamera/colmap_processing/geo_conversions.py @@ -5,6 +5,7 @@ try: from sklearn.preprocessing import PolynomialFeatures + sklearn_imported = True except ImportError: sklearn_imported = False @@ -15,16 +16,16 @@ # WGS84 constants _a = 6378137 -_f = 1/(298257223563/1000000000) -_e2 = _f*(2-_f) -_e2m = np.square(1-_f) +_f = 1 / (298257223563 / 1000000000) +_e2 = _f * (2 - _f) +_e2m = np.square(1 - _f) _e2a = abs(_e2) _e4a = np.square(_e2) epsilon = np.finfo(float).eps _maxrad = 2 * _a / epsilon -deg2rad = np.pi/180 -rad2deg = 180/np.pi +deg2rad = np.pi / 180 +rad2deg = 180 / np.pi class FastENUConverter(object): @@ -66,8 +67,17 @@ class FastENUConverter(object): """ - def __init__(self, lat_range, lon_range, height_range, lat0, lon0, h0, - accuracy=[1e-2, 1e-2, 1e-2]): + + def __init__( + self, + lat_range, + lon_range, + height_range, + lat0, + lon0, + h0, + accuracy=[1e-2, 1e-2, 1e-2], + ): """ :param lat_range: Range of latitudes (degrees) to support. :type lat_range: array-like shape (2,) @@ -92,7 +102,7 @@ def __init__(self, lat_range, lon_range, height_range, lat0, lon0, h0, for h in hs: LATS, LONS = np.meshgrid(lats, lons) L = len(LATS.ravel()) - llhi = np.vstack([LATS.ravel(), LONS.ravel(), np.ones(L)*h]).T + llhi = np.vstack([LATS.ravel(), LONS.ravel(), np.ones(L) * h]).T llh = np.vstack([llh, llhi]) enu = [llh_to_enu(_[0], _[1], _[2], lat0, lon0, h0) for _ in llh] @@ -105,9 +115,10 @@ def __init__(self, lat_range, lon_range, height_range, lat0, lon0, h0, degree += 1 if degree == 10: - raise Exception('Failed to fit to required accuracy=%0.3f. ' - 'Try reducing required accuracy.' % - accuracy) + raise Exception( + "Failed to fit to required accuracy=%0.3f. " + "Try reducing required accuracy." % accuracy + ) self._llh_feature_poly = PolynomialFeatures(degree=degree) features = self._llh_feature_poly.fit_transform(llh) @@ -132,9 +143,10 @@ def __init__(self, lat_range, lon_range, height_range, lat0, lon0, h0, degree += 1 if degree == 10: - raise Exception('Failed to fit to required accuracy=%0.3f. ' - 'Try reducing required accuracy.' % - accuracy) + raise Exception( + "Failed to fit to required accuracy=%0.3f. " + "Try reducing required accuracy." % accuracy + ) self._enu_feature_poly = PolynomialFeatures(degree=degree) features = self._enu_feature_poly.fit_transform(enu) @@ -146,15 +158,14 @@ def __init__(self, lat_range, lon_range, height_range, lat0, lon0, h0, self._enu_coeff = np.array(coeff).T fit = np.dot(features, self._enu_coeff) - enu_fit = [llh_to_enu(_[0], _[1], _[2], lat0, lon0, h0) - for _ in fit] + enu_fit = [llh_to_enu(_[0], _[1], _[2], lat0, lon0, h0) for _ in fit] if np.any(np.abs(enu_fit - enu) > accuracy): continue break def llh_to_enu(self, lat, lon, h): - if hasattr(lat, '__len__'): + if hasattr(lat, "__len__"): llh = np.vstack([lat, lon, h]).T features = self._llh_feature_poly.transform(llh) enu = np.dot(features, self._llh_coeff) @@ -166,7 +177,7 @@ def llh_to_enu(self, lat, lon, h): return float(enu[0, 0]), float(enu[0, 1]), float(enu[0, 2]) def enu_to_llh(self, east, north, up): - if hasattr(east, '__len__'): + if hasattr(east, "__len__"): enu = np.vstack([east, north, up]).T features = self._enu_feature_poly.transform(enu) enu = np.dot(features, self._enu_coeff) @@ -227,32 +238,40 @@ def llh_to_enu(lat, lon, h, lat0, lon0, h0, in_degrees=True, pure_python=True): """ if not in_degrees: - lat = lat*180/np.pi - lon = lon*180/np.pi - lat0 = lat0*180/np.pi - lon0 = lon0*180/np.pi + lat = lat * 180 / np.pi + lon = lon * 180 / np.pi + lat0 = lat0 * 180 / np.pi + lon0 = lon0 * 180 / np.pi if pure_python: sphi, cphi = sincosd(lat0) slam, clam = sincosd(lon0) _r = geocentric_rotation(sphi, cphi, slam, clam) - xc,yc,zc = llh_to_ecef(lat, lon, h, in_degrees=True) - _x0,_y0,_z0 = llh_to_ecef(lat0, lon0, h0, in_degrees=True) - xc -= _x0; yc -= _y0; zc -= _z0; - x = _r[0] * xc + _r[3] * yc + _r[6] * zc; - y = _r[1] * xc + _r[4] * yc + _r[7] * zc; - z = _r[2] * xc + _r[5] * yc + _r[8] * zc; - return [x,y,z] + xc, yc, zc = llh_to_ecef(lat, lon, h, in_degrees=True) + _x0, _y0, _z0 = llh_to_ecef(lat0, lon0, h0, in_degrees=True) + xc -= _x0 + yc -= _y0 + zc -= _z0 + x = _r[0] * xc + _r[3] * yc + _r[6] * zc + y = _r[1] * xc + _r[4] * yc + _r[7] * zc + z = _r[2] * xc + _r[5] * yc + _r[8] * zc + return [x, y, z] else: - output = subprocess.check_output(['CartConvert','-l', - str(lat0),str(lon0), - str(h0),'--input-string', - ' '.join([str(lat),str(lon),str(h)])]) - return [float(s) for s in output.split('\n')[0].split(' ')] - - -def enu_to_llh(east, north, up, lat0, lon0, h0, in_degrees=True, - pure_python=True): + output = subprocess.check_output( + [ + "CartConvert", + "-l", + str(lat0), + str(lon0), + str(h0), + "--input-string", + " ".join([str(lat), str(lon), str(h)]), + ] + ) + return [float(s) for s in output.split("\n")[0].split(" ")] + + +def enu_to_llh(east, north, up, lat0, lon0, h0, in_degrees=True, pure_python=True): """Convert latitude, longitude, and height to east, north, up. East, north, and up are coordinates within a local level Cartesian @@ -302,36 +321,46 @@ def enu_to_llh(east, north, up, lat0, lon0, h0, in_degrees=True, """ if not in_degrees: - lat0 = lat0*180/np.pi - lon0 = lon0*180/np.pi + lat0 = lat0 * 180 / np.pi + lon0 = lon0 * 180 / np.pi if pure_python: x, y, z = east, north, up sphi, cphi = sincosd(lat0) slam, clam = sincosd(lon0) _r = geocentric_rotation(sphi, cphi, slam, clam) - _x0,_y0,_z0 = llh_to_ecef(lat0, lon0, h0, in_degrees=True) + _x0, _y0, _z0 = llh_to_ecef(lat0, lon0, h0, in_degrees=True) - xc = _x0 + _r[0] * x + _r[1] * y + _r[2] * z, - yc = _y0 + _r[3] * x + _r[4] * y + _r[5] * z, - zc = _z0 + _r[6] * x + _r[7] * y + _r[8] * z; + xc = (_x0 + _r[0] * x + _r[1] * y + _r[2] * z,) + yc = (_y0 + _r[3] * x + _r[4] * y + _r[5] * z,) + zc = _z0 + _r[6] * x + _r[7] * y + _r[8] * z lat, lon, h = ecef_to_llh(xc, yc, zc, in_degrees) else: - output = subprocess.check_output(['CartConvert','-r','-l',str(lat0), - str(lon0),str(h0),'--input-string', - ' '.join([str(east),str(north), - str(up)])]) - - lat, lon, h = [float(s) for s in output.split('\n')[0].split(' ')] + output = subprocess.check_output( + [ + "CartConvert", + "-r", + "-l", + str(lat0), + str(lon0), + str(h0), + "--input-string", + " ".join([str(east), str(north), str(up)]), + ] + ) + + lat, lon, h = [float(s) for s in output.split("\n")[0].split(" ")] if not in_degrees: - lat = lat*180/np.pi - lon = lon*180/np.pi + lat = lat * 180 / np.pi + lon = lon * 180 / np.pi + + return [lat, lon, h] - return [lat,lon,h] + +_cached1 = _a * (1 - _e2) -_cached1 = _a*(1-_e2) def dlat_dlon_per_meter(lat, in_degrees=True): """Return latitude and longitude degrees change per meter east and north. @@ -352,18 +381,18 @@ def dlat_dlon_per_meter(lat, in_degrees=True): """ if in_degrees: - lat_ = lat*deg2rad + lat_ = lat * deg2rad - east = _cached1*(1 - _e2*sin(lat_)**2)**(-3/2) + east = _cached1 * (1 - _e2 * sin(lat_) ** 2) ** (-3 / 2) # Parameter (or reduced) latitude. - beta = np.arctan((1-_f)*np.tan(lat_)) + beta = np.arctan((1 - _f) * np.tan(lat_)) - north = _a*np.cos(beta) + north = _a * np.cos(beta) if in_degrees: - east = east*deg2rad - north = north*deg2rad + east = east * deg2rad + north = north * deg2rad return east, north @@ -379,7 +408,7 @@ def ned_quat_to_enu_quat(quat): :rtype: 4-array """ - return quaternion_multiply([np.sqrt(2)/2,np.sqrt(2)/2,0,0], quat) + return quaternion_multiply([np.sqrt(2) / 2, np.sqrt(2) / 2, 0, 0], quat) def enu_quat_to_ned_quat(quat): @@ -393,7 +422,7 @@ def enu_quat_to_ned_quat(quat): :rtype: 4-array """ - return quaternion_multiply([np.sqrt(2)/2,np.sqrt(2)/2,0,0], quat) + return quaternion_multiply([np.sqrt(2) / 2, np.sqrt(2) / 2, 0, 0], quat) def ecef_to_llh(X, Y, Z, in_degrees=True): @@ -422,7 +451,7 @@ def ecef_to_llh(X, Y, Z, in_degrees=True): z = 2167698 """ - R = np.hypot(X,Y) + R = np.hypot(X, Y) if R == 0: slam = 0 clam = 1 @@ -430,25 +459,25 @@ def ecef_to_llh(X, Y, Z, in_degrees=True): slam = Y / R clam = X / R - h = np.hypot(R,Z) # Distance to center of earth - if (h > _maxrad): + h = np.hypot(R, Z) # Distance to center of earth + if h > _maxrad: # We really far away (> 12 million light years) treat the earth as a # point and h, above, is an acceptable approximation to the height. # This avoids overflow, e.g., in the computation of disc below. It's # possible that h has overflowed to inf but that's OK. # # Treat the case X, Y finite, but R overflows to +inf by scaling by 2. - R = np.hypot(X/2, Y/2) + R = np.hypot(X / 2, Y / 2) if R == 0: slam = 0 clam = 1 else: - slam = (Y/2) / R - clam = (X/2) / R + slam = (Y / 2) / R + clam = (X / 2) / R - H = np.hypot(Z/2,R) - sphi = (Z/2) / H + H = np.hypot(Z / 2, R) + sphi = (Z / 2) / H cphi = R / H elif _e4a == 0: # Treat the spherical case. Dealing with underflow in the general case @@ -470,17 +499,17 @@ def ecef_to_llh(X, Y, Z, in_degrees=True): q = _e2m * np.square(Z / _a) r = (p + q - _e4a) / 6 if _f < 0: - p,q = q,p + p, q = q, p if not (_e4a * q == 0 and r <= 0): # Avoid possible division by zero when r = 0 by multiplying # equations for s and t by r^3 and r, resp. - S = _e4a * p * q / 4 # S = r^3 * s + S = _e4a * p * q / 4 # S = r^3 * s r2 = np.square(r) r3 = r * r2 disc = S * (2 * r3 + S) u = r - if (disc >= 0): + if disc >= 0: T3 = S + r3 # Pick the sign on the sqrt to maximize abs(T3). This # minimizes loss of precision due to cancellation. The result @@ -492,7 +521,7 @@ def ecef_to_llh(X, Y, Z, in_degrees=True): T3 += np.sqrt(disc) # N.B. cbrt always returns the real root. cbrt(-8) = -2. - T = np.cbrt(T3) # T = r * t + T = np.cbrt(T3) # T = r * t # T can be zero but then r2 / T -> 0. if T != 0: u += T + (r2 / T) @@ -505,7 +534,7 @@ def ecef_to_llh(X, Y, Z, in_degrees=True): # r < 0. u += 2 * r * np.cos(ang / 3) - v = np.sqrt(np.square(u) + _e4a * q) # guaranteed positive + v = np.sqrt(np.square(u) + _e4a * q) # guaranteed positive # Avoid loss of accuracy when u < 0. Underflow doesn't occur in # e4 * q / (v - u) because u ~ e^4 when q is small and u < 0. if u < 0: # u+v guaranteed positive @@ -526,12 +555,12 @@ def ecef_to_llh(X, Y, Z, in_degrees=True): k2 = k d = k1 * R / k2 - H = np.hypot(Z/k1, R/k2) - sphi = (Z/k1) / H - cphi = (R/k2) / H - h = (1 - _e2m/k1) * np.hypot(d, Z) + H = np.hypot(Z / k1, R / k2) + sphi = (Z / k1) / H + cphi = (R / k2) / H + h = (1 - _e2m / k1) * np.hypot(d, Z) - else: # e4 * q == 0 && r <= 0 + else: # e4 * q == 0 && r <= 0 # This leads to k = 0 (oblate, equatorial plane) and k + e^2 = 0 # (prolate, rotation axis) and the generation of 0/0 in the general # formulas for phi and h. using the general formula and division by 0 @@ -543,7 +572,7 @@ def ecef_to_llh(X, Y, Z, in_degrees=True): else: zz = np.sqrt(p / _e2m) - if _f < 0: + if _f < 0: xx = np.sqrt(_e4a - p) else: xx = np.sqrt(p) @@ -552,15 +581,15 @@ def ecef_to_llh(X, Y, Z, in_degrees=True): sphi = zz / H cphi = xx / H if Z < 0: - sphi = -sphi # for tiny negative Z (not for prolate) + sphi = -sphi # for tiny negative Z (not for prolate) if _f >= 0: - h = - _a * (_e2m) * H / _e2a + h = -_a * (_e2m) * H / _e2a else: - h = - _a * (1) * H / _e2a + h = -_a * (1) * H / _e2a - lat = float(np.arctan2(sphi, cphi)*180/np.pi) - lon = float(np.arctan2(slam, clam)*180/np.pi) + lat = float(np.arctan2(sphi, cphi) * 180 / np.pi) + lon = float(np.arctan2(slam, clam) * 180 / np.pi) h = float(h) return lat, lon, h @@ -586,18 +615,18 @@ def llh_to_ecef(lat, lon, h, in_degrees=True): """ if not in_degrees: - lat = lat*180/np.pi - lon = lon*180/np.pi + lat = lat * 180 / np.pi + lon = lon * 180 / np.pi - sphi,cphi = sincosd(lat) - slam,clam = sincosd(lon) + sphi, cphi = sincosd(lat) + slam, clam = sincosd(lon) - n = _a/np.sqrt(1-_e2*np.square(sphi)) + n = _a / np.sqrt(1 - _e2 * np.square(sphi)) Z = (_e2m * n + h) * sphi X = (n + h) * cphi Y = X * slam X *= clam - return [float(X),float(Y),float(Z)] + return [float(X), float(Y), float(Z)] def geocentric_rotation(sphi, cphi, slam, clam): @@ -612,16 +641,22 @@ def geocentric_rotation(sphi, cphi, slam, clam): """ M = np.zeros(9) # Local X axis (east) in geocentric coords - M[0] = -slam; M[3] = clam; M[6] = 0; + M[0] = -slam + M[3] = clam + M[6] = 0 # Local Y axis (north) in geocentric coords - M[1] = -clam * sphi; M[4] = -slam * sphi; M[7] = cphi; + M[1] = -clam * sphi + M[4] = -slam * sphi + M[7] = cphi # Local Z axis (up) in geocentric coords - M[2] = clam * cphi; M[5] = slam * cphi; M[8] = sphi; + M[2] = clam * cphi + M[5] = slam * cphi + M[8] = sphi return M def sincosd(x): - """ + r""" * Evaluate the sine and cosine function with the argument in degrees * * @tparam T the type of the arguments. @@ -648,13 +683,17 @@ def sincosd(x): s = x if np.uint8(q) & np.uint8(3) == np.uint(0): - sinx = s; cosx = c + sinx = s + cosx = c elif np.uint8(q) & np.uint8(3) == np.uint(1): - sinx = c; cosx = -s + sinx = c + cosx = -s elif np.uint8(q) & np.uint8(3) == np.uint(2): - sinx = -s; cosx = -c + sinx = -s + cosx = -c else: - sinx = -c; cosx = s + sinx = -c + cosx = s # Set sign of 0 results. -0 only produced for sin(-0) if x: @@ -679,17 +718,21 @@ def rmat_enu_ecef(lat, lon, in_degrees=True): """ if in_degrees: - lat = lat/180*np.pi - lon = lon/180*np.pi + lat = lat / 180 * np.pi + lon = lon / 180 * np.pi clat = cos(lat) slat = sin(lat) clon = cos(lon) slon = sin(lon) - return np.array([[-slon, -slat*clon, clat*clon], - [clon, -slat*slon, clat*slon], - [0, clat, slat]]) + return np.array( + [ + [-slon, -slat * clon, clat * clon], + [clon, -slat * slon, clat * slon], + [0, clat, slat], + ] + ) def rmat_ecef_enu(lat, lon, in_degrees=True): @@ -707,17 +750,21 @@ def rmat_ecef_enu(lat, lon, in_degrees=True): """ if in_degrees: - lat = lat/180*np.pi - lon = lon/180*np.pi + lat = lat / 180 * np.pi + lon = lon / 180 * np.pi clat = cos(lat) slat = sin(lat) clon = cos(lon) slon = sin(lon) - return np.array([[-slon, clon, 0], - [-clon*slat, -slon*slat, clat], - [clon*clat, slon*clat, slat]]) + return np.array( + [ + [-slon, clon, 0], + [-clon * slat, -slon * slat, clat], + [clon * clat, slon * clat, slat], + ] + ) def quat_std_to_ypr_std(quat, qx_std, qy_std, qz_std, qw_std=None): @@ -770,53 +817,59 @@ def yaw(qx, qy, qz, qw): qx2 = qx**2 qy2 = qy**2 qz2 = qz**2 - qwqy = qw*qy - qxqz = qx*qz - qyqz = qy*qz - qwqx = qw*qx - qwqz = qw*qz - qxqy = qx*qy - C1 = qwqz+qxqy - C2 = qz2+qy2 - C4 = qyqz+qwqx - C5 = qy2+qx2 - C6 = 1-2*(C2) - C7 = C6**2+4*C1**2 - C8 = (1-2*C5)**2 - C9 = 1-2*C5 - C10 = 4*C4**2+C8 - C11 = C9/C10 - C12 = C4*8 + qwqy = qw * qy + qxqz = qx * qz + qyqz = qy * qz + qwqx = qw * qx + qwqz = qw * qz + qxqy = qx * qy + C1 = qwqz + qxqy + C2 = qz2 + qy2 + C4 = qyqz + qwqx + C5 = qy2 + qx2 + C6 = 1 - 2 * (C2) + C7 = C6**2 + 4 * C1**2 + C8 = (1 - 2 * C5) ** 2 + C9 = 1 - 2 * C5 + C10 = 4 * C4**2 + C8 + C11 = C9 / C10 + C12 = C4 * 8 # Heading - dheading_dx = (2*qy*C6)/C7 - dheading_dy = (2*qx*C6)/C7+(8*qy*C1)/C7 - dheading_dz = (2*qw*C6)/C7+(8*qz*C1)/C7 - dheading_dw = (2*qz*C6)/C7 + dheading_dx = (2 * qy * C6) / C7 + dheading_dy = (2 * qx * C6) / C7 + (8 * qy * C1) / C7 + dheading_dz = (2 * qw * C6) / C7 + (8 * qz * C1) / C7 + dheading_dw = (2 * qz * C6) / C7 # Pitch - C3 = sqrt(1-4*(qwqy-qxqz)**2) - dpitch_dx = -2*qz/C3 - dpitch_dy = 2*qw/C3 - dpitch_dz = -2*qx/C3 - dpitch_dw = 2*qy/C3 + C3 = sqrt(1 - 4 * (qwqy - qxqz) ** 2) + dpitch_dx = -2 * qz / C3 + dpitch_dy = 2 * qw / C3 + dpitch_dz = -2 * qx / C3 + dpitch_dw = 2 * qy / C3 # Change in roll as function of qx - droll_dx = qx*C12/C10+2*qw*C11 - droll_dy = qy*C12/C10+2*qz*C11 - droll_dz = 2*qy*C11 - droll_dw = 2*qx*C11 - - heading_std = abs(dheading_dx - dheading_dy/3 - dheading_dz/3 - dheading_dw/3)*qx_std - heading_std += abs(dheading_dy - dheading_dx/3 - dheading_dz/3 - dheading_dw/3)*qy_std - heading_std += abs(dheading_dz - dheading_dx/3 - dheading_dy/3 - dheading_dw/3)*qz_std - - pitch_std = abs(dpitch_dx - dpitch_dy/3 - dpitch_dz/3 - dpitch_dw/3)*qx_std - pitch_std += abs(dpitch_dy - dpitch_dx/3 - dpitch_dz/3 - dpitch_dw/3)*qy_std - pitch_std += abs(dpitch_dz - dpitch_dx/3 - dpitch_dy/3 - dpitch_dw/3)*qz_std - - roll_std = abs(droll_dx - droll_dy/3 - droll_dz/3 - droll_dw/3)*qx_std - roll_std += abs(droll_dy - droll_dx/3 - droll_dz/3 - droll_dw/3)*qy_std - roll_std += abs(droll_dz - droll_dx/3 - droll_dy/3 - droll_dw/3)*qz_std + droll_dx = qx * C12 / C10 + 2 * qw * C11 + droll_dy = qy * C12 / C10 + 2 * qz * C11 + droll_dz = 2 * qy * C11 + droll_dw = 2 * qx * C11 + + heading_std = ( + abs(dheading_dx - dheading_dy / 3 - dheading_dz / 3 - dheading_dw / 3) * qx_std + ) + heading_std += ( + abs(dheading_dy - dheading_dx / 3 - dheading_dz / 3 - dheading_dw / 3) * qy_std + ) + heading_std += ( + abs(dheading_dz - dheading_dx / 3 - dheading_dy / 3 - dheading_dw / 3) * qz_std + ) + + pitch_std = abs(dpitch_dx - dpitch_dy / 3 - dpitch_dz / 3 - dpitch_dw / 3) * qx_std + pitch_std += abs(dpitch_dy - dpitch_dx / 3 - dpitch_dz / 3 - dpitch_dw / 3) * qy_std + pitch_std += abs(dpitch_dz - dpitch_dx / 3 - dpitch_dy / 3 - dpitch_dw / 3) * qz_std + + roll_std = abs(droll_dx - droll_dy / 3 - droll_dz / 3 - droll_dw / 3) * qx_std + roll_std += abs(droll_dy - droll_dx / 3 - droll_dz / 3 - droll_dw / 3) * qy_std + roll_std += abs(droll_dz - droll_dx / 3 - droll_dy / 3 - droll_dw / 3) * qz_std return heading_std, pitch_std, roll_std diff --git a/kamera/colmap_processing/test/test_camera_models.py b/kamera/colmap_processing/test/test_camera_models.py deleted file mode 100644 index 438c4c03..00000000 --- a/kamera/colmap_processing/test/test_camera_models.py +++ /dev/null @@ -1,98 +0,0 @@ -#! /usr/bin/python -from __future__ import division, print_function -import numpy as np -import os -import cv2 -import matplotlib.pyplot as plt -from osgeo import osr, gdal -from scipy.optimize import fmin, minimize, fminbound -import transformations - -# Colmap Processing imports. -from colmap_processing.geo_conversions import llh_to_enu -from colmap_processing.colmap_interface import read_images_binary, Image, \ - read_points3d_binary, read_cameras_binary, qvec2rotmat -from colmap_processing.camera_models import StandardCamera -from colmap_processing.platform_pose import PlatformPoseInterp - - -# ---------------------------------------------------------------------------- -# Define the directory where all of the relavant COLMAP files are saved. -# If you are placing your data within the 'data' folder of this repository, -# this will be mapped to '/home_user/adapt_postprocessing/data' inside the -# Docker container. -data_dir = '/media/data' - -image_subdirs = ['1', '2'] - -# COLMAP data directory. -images_bin_fname = '%s/images.bin' % data_dir -camera_bin_fname = '%s/cameras.bin' % data_dir -points_bin_fname = '%s/points3D.bin' % data_dir -# ---------------------------------------------------------------------------- - - -# Read in the details of all images. -images = read_images_binary(images_bin_fname) -cameras = read_cameras_binary(camera_bin_fname) -points = read_points3d_binary(points_bin_fname) - - -# Pretend image index is the time. -platform_pose_provider = PlatformPoseInterp() -for image_id in images: - image = images[image_id] - - R = qvec2rotmat(image.qvec) - pos = -np.dot(R.T, image.tvec) - - # The qvec used by Colmap is a (w, x, y, z) quaternion representing the - # rotation of a vector defined in the world coordinate system into the - # camera coordinate system. However, the 'camera_models' module assumes - # (x, y, z, w) quaternions representing a coordinate system rotation. - quat = transformations.quaternion_inverse(image.qvec) - quat = [quat[1], quat[2], quat[3], quat[0]] - - t = image_id - platform_pose_provider.add_to_pose_time_series(t, pos, quat) - - -std_cams = {} -for camera_id in set([images[image_id].camera_id for image_id in images]): - colmap_camera = cameras[image.camera_id] - - if colmap_camera.model == 'OPENCV': - fx, fy, cx, cy, d1, d2, d3, d4 = colmap_camera.params - - K = K = np.array([[fx, 0, cx], [0, fy, cy], [0, 0, 1]]) - dist = np.array([d1, d2, d3, d4]) - std_cams[image.camera_id] = StandardCamera(colmap_camera.width, - colmap_camera.height, K, dist, - [0, 0, 0], [0, 0, 0, 1], - platform_pose_provider) - - -# Calculate reprojection error. -for image_id in images: - image = images[image_id] - colmap_camera = cameras[image.camera_id] - fname = '%s/%s.txt' % (data_dir, os.path.splitext(image.name)[0]) - R = qvec2rotmat(image.qvec) - - ind = image.point3D_ids >= 0 - - im_pts = image.xys[ind].T - wrld_pts = np.array([points[i].xyz for i in image.point3D_ids[ind]]).T - - if colmap_camera.model == 'OPENCV': - fx, fy, cx, cy, d1, d2, d3, d4 = colmap_camera.params - - K = np.array([[fx, 0, cx], [0, fy, cy], [0, 0, 1]]) - dist = np.array([d1, d2, d3, d4]) - tvec = image.tvec - rvec = cv2.Rodrigues(R)[0] - im_pts2 = np.squeeze(cv2.projectPoints(wrld_pts.T, rvec, tvec, K, dist)[0]).T - err = np.sqrt(np.sum(im_pts2 - im_pts, axis=0)) - - std_cams[image.camera_id].project(wrld_pts, t=image_id) - diff --git a/kamera/postflight/scripts/calibrate_from_colmap.py b/kamera/postflight/scripts/calibrate_from_colmap.py deleted file mode 100644 index fec90eab..00000000 --- a/kamera/postflight/scripts/calibrate_from_colmap.py +++ /dev/null @@ -1,242 +0,0 @@ -#!/usr/bin/env python -""" -Library handling projection operations of a standard camera model. - -Note: the image coordiante system has its origin at the center of the top left -pixel. -""" -from __future__ import division, print_function -import numpy as np -from numpy import pi -import matplotlib.pyplot as plt -from mpl_toolkits.mplot3d import Axes3D -import cv2 -import time -import os -import glob -import matplotlib.pyplot as plt -import bisect -import json -from scipy.optimize import minimize - -# Custom package imports. -from sensor_models import ( - quaternion_multiply, - quaternion_from_matrix, - quaternion_from_euler, - quaternion_slerp, - quaternion_inverse, - quaternion_matrix - ) -from colmap_processing.camera_models import load_from_file, StandardCamera -from sensor_models.nav_state import NavStateINSBinary, NavStateINSJson - - -class ColmapImage(object): - def __init__(self, image_id, qw, qx, qy, qz, tx, ty, tz, cam_id, name, - pts): - self.image_id = image_id - self.qw = qw - self.qx = qx - self.qy = qy - self.qz = qz - self.tx = tx - self.ty = ty - self.tz = tz - self.cam_id = cam_id - self.name = name - self.pts = pts - - def get_camera_pose(self): - R = quaternion_matrix([self.qx,self.qy,self.qz,self.qw])[:3,:3] - return np.hstack([R,np.array([[self.tx,self.ty,self.tz]]).T]) - - def pos(self): - return np.dot(R.T,-t) - - -# Colmap text camera model directory. -flight_dir = '00' -colmap_dir = '00/colmap' -camera_model_dir = '/root/kamera/src/cfg/camera_models' - -# Read in the nav binary. -#nav_state_provider = NavStateINSBinary(nav_binary_fname) - -# Recover the mapping between filename and high-precision time. -image_globs = ['%s/CENT/*rgb.tif' % flight_dir, - '%s/LEFT/*rgb.tif' % flight_dir, - '%s/RIGHT/*rgb.tif' % flight_dir] -camera_model_fnames = ['%s/left_sys/rgb.yaml' % camera_model_dir, - '%s/center_sys/rgb.yaml' % camera_model_dir, - '%s/right_sys/rgb.yaml' % camera_model_dir] - -img_fnames = {} -fname_to_time = {} -camera_models = [] -for i in range(3): - image_glob = image_globs[i] - nav_dir = os.path.split(image_glob)[0] - nav_state_provider = NavStateINSJson('%s/*meta.json' % nav_dir) - - for img_fname in glob.glob(image_glob): - json_fname = img_fname - json_fname = json_fname.replace('rgb.tif', 'meta.json') - json_fname = json_fname.replace('ir.tif', 'meta.json') - json_fname = json_fname.replace('uv.tif', 'meta.json') - - try: - with open(json_fname) as json_file: - d = json.load(json_file) - - # Time that the image was taken. - img_fnames[d['evt']['time']] = img_fname - fname = os.path.splitext(os.path.split(img_fname)[1])[0] - fname_to_time[fname] = d['evt']['time'] - except OSError: - pass - - lat0 = nav_state_provider.lat0 - lon0 = nav_state_provider.lon0 - h0 = nav_state_provider.h0 - - camera_models.append(load_from_file(camera_model_fnames[i], - nav_state_provider)) - - - -if False: - camera_model_fnames = '%s/cameras.txt' % colmap_dir - camera_models = [] - with open(camera_model_fnames, 'r') as infile: - for line in infile: - if not line.startswith('#'): - p = np.array(line.split('\n')[0].split(' ')[2:], np.float) - width, height, fx, fy, cx, cy, k1, k2, k3, k4 = p - K = np.array([[fx,0,cx],[0,fy,cy],[0,0,1]]) - image_topic = None - camera_models.append(StandardCamera(width, height, K, - (k1,k2,k3,k4), (0,0,0), - (1,0,0,0), image_topic, - frame_id=None, - nav_state_provider=nav_state_provider)) - - -image_fname = '%s/output/images.txt' % colmap_dir -colmap_images = [] -with open(image_fname, 'r') as infile: - while True: - line = infile.readline() - if line == '': - break - - if not line.startswith('#'): - # IMAGE_ID, QW, QX, QY, QZ, TX, TY, TZ, CAMERA_ID, NAME - # POINTS2D[] as (X, Y, POINT3D_ID) - p = line.split('\n')[0].split(' ') - image_id, qw, qx, qy, qz, tx, ty, tz, cam_id, name = p - qw, qx, qy, qz, tx, ty, tz = [float(_) for _ in (qw, qx, qy, qz, tx, ty, tz)] - cam_id = int(cam_id) - - line = infile.readline() - p = line.split('\n')[0].split(' ') - pts = np.reshape(np.array([float(_) for _ in p]), (-1,3)) - - colmap_image = ColmapImage(image_id, qw, qx, qy, qz, tx, ty, tz, - cam_id, name, pts) - colmap_images.append(colmap_image) - - -points_fname = '%s/points3D.txt' % colmap_dir -points = [] -with open(points_fname, 'r') as infile: - for line in infile: - pass - - -nav_times = nav_state_provider.pose_time_series[:,0] - - -camera_model = camera_models[0] - - -def err_fun(cam_quat): - #cam_quat = np.hstack([0.5, cam_quat]) - cam_quat /= np.linalg.norm(cam_quat) - camera_model.update_intrinsics(cam_quat=cam_quat) - theta = 0 - for i in range(len(colmap_images)): - colmap_image = colmap_images[i] - fname = os.path.splitext(os.path.split(colmap_image.name)[1])[0] - t = fname_to_time[fname] - P1 = camera_models[0].get_camera_pose(t) - P2 = colmap_image.get_camera_pose() - R1 = np.identity(4); R1[:3,:3] = P1[:,:3] - R2 = np.identity(4); R2[:3,:3] = P2[:,:3] - q1 = quaternion_from_matrix(R1) - q2 = quaternion_from_matrix(R2) - dq = quaternion_multiply(q1, quaternion_inverse(q2)) - theta += 2*np.arccos(max([min([dq[3],1]),-1])) - - theta /= len(colmap_images) - print(cam_quat, theta) - return theta - -if False: - min_err = np.inf - for _ in range(10000): - cam_quat = random_quaternion() - err = err_fun(cam_quat) - if err < min_err: - min_err = err - best_cam_quat = cam_quat - else: - cam_quat = camera_model.cam_quat - -x = minimize(err_fun, cam_quat, tol=1-9).x - - -plt.plot(nav_state_provider.pose_time_series[:,3]) - -# ---------------------------------------------------------------------------- -fig = plt.figure() -ax = fig.add_subplot(111, projection='3d') -r = 5 -times = nav_times -times = [image_times[_.name] for _ in colmap_images] -times = np.sort(times) -pos = np.array([nav_state_provider.pose(t)[0] for t in times]).T - -plt.plot(pos[0], pos[1], pos[2], 'k-') -plt.plot(pos[0], pos[1], pos[2], 'ro') - -for t in times: - pos,quat = nav_state_provider.pose(t) - R = quaternion_matrix(quaternion_inverse(quat))[:3,:3] - - s = ['r-','g-','b-'] - for i in range(3): - plt.plot([pos[0],pos[0]+R[i][0]*r], [pos[1],pos[1]+R[i][1]*r], - [pos[2],pos[2]+R[i][2]*r], s[i], linewidth=3) - -plt.xlabel('Easting') -plt.ylabel('Northing') - - -fig = plt.figure() -ax = fig.add_subplot(111, projection='3d') -r = 5 -for i in range(len(colmap_images)): - colmap_image = colmap_images[i] - P = colmap_image.get_camera_pose() - R = np.identity(4); R[:3,:3] = P[:,:3] - pos = colmap_image.pos() - plt.plot([pos[0]], [pos[1]], [pos[2]], 'ro') - - s = ['r-','g-','b-'] - for i in range(3): - plt.plot([pos[0],pos[0]+R[i][0]*r], [pos[1],pos[1]+R[i][1]*r], - [pos[2],pos[2]+R[i][2]*r], s[i], linewidth=3) - -plt.xlabel('Easting') -plt.ylabel('Northing') diff --git a/kamera/postflight/scripts/calibrate_ir_from_rgb.py b/kamera/postflight/scripts/calibrate_ir_from_rgb.py deleted file mode 100644 index fbf0d96f..00000000 --- a/kamera/postflight/scripts/calibrate_ir_from_rgb.py +++ /dev/null @@ -1,169 +0,0 @@ -#!/usr/bin/env python -""" -Library handling projection operations of a standard camera model. -""" -from __future__ import division, print_function -import copy -import cv2 -import time -import os -import glob -import random -import json -import PIL -import numpy as np -import matplotlib.pyplot as plt -from numpy import pi -from mpl_toolkits.mplot3d import Axes3D -from scipy.optimize import minimize - -# Custom package imports. -from sensor_models import ( - quaternion_multiply, - quaternion_from_matrix, - quaternion_from_euler, - quaternion_slerp, - quaternion_inverse, - quaternion_matrix - ) -from colmap_processing.camera_models import load_from_file, StandardCamera -from colmap_processing.image_renderer import render_view - - -def calibrate_ir(rgb_camera_model_fname, ir_camera_model_fname, - image_point_pairs_fname): - image_pts = np.loadtxt(image_point_pairs_fname) - - rgb_camera = load_from_file(rgb_camera_model_fname) - ir_camera = load_from_file(ir_camera_model_fname) - - def get_new_cm(x): - tmp_cm = copy.deepcopy(ir_camera) - cam_quat = x[:4] - cam_quat /= np.linalg.norm(cam_quat) - tmp_cm.update_intrinsics(cam_quat=cam_quat) - - if len(x) > 4: - tmp_cm.fx = x[4] - - if len(x) > 5: - tmp_cm.fy = x[5] - - return tmp_cm - - def proj_err(x): - tmp_cm = get_new_cm(x) - - wrld_pts = rgb_camera.unproject(image_pts[:, 2:].T)[1] - im_pts = tmp_cm.project(wrld_pts) - - err = np.sqrt(np.sum(np.sum((image_pts[:, :2].T - im_pts)**2, 1))) - err /= len(wrld_pts) - print(err, x) - return err - - min_err = np.inf - best_x = None - for _ in range(10000): - x = np.random.rand(4)*2-1 - x /= np.linalg.norm(x) - err = proj_err(x) - if err < min_err: - min_err = err - best_x = x - - if True: - x = np.hstack([best_x, ir_camera.fx, ir_camera.fy]) - else: - x = best_x - - x = minimize(proj_err, x).x - x = minimize(proj_err, x, method='Powell').x - x = minimize(proj_err, x, method='BFGS').x - x = minimize(proj_err, x).x - - tmp_cm = get_new_cm(x) - - print('Final mean error:', proj_err(x), 'pixels') - tmp_cm.image_topic = '' - tmp_cm.frame_id = '' - tmp_cm.save_to_file(ir_camera_model_fname) - print('Saved updated camera model to', ir_camera_model_fname) - - -# Process three RGB cameras. -rgb_camera_model_fname = '' -ir_camera_model_fname = '' -image_point_pairs_fname = '' -calibrate_ir(rgb_camera_model_fname, ir_camera_model_fname, - image_point_pairs_fname) -# ---------------------------------- IR Gifs --------------------------------- - -# Location to save KAMERA camera models. -rgb_img_dir = '' -ir_img_dir = '' - -base_dir, base = os.path.split(ir_camera_model_fname) -out_dir = '%s/registration_gifs/%s' % (base_dir, os.path.splitext(base)[0]) -num_gifs = 50 - -try: - os.makedirs(out_dir) -except (IOError, OSError): - pass - - -def stretch_contrast(img, clip_limit=3, stretch_percentiles=[0.1, 99.9]): - img = img.astype(np.float32) - img -= np.percentile(img.ravel(), stretch_percentiles[0]) - img[img < 0] = 0 - img /= np.percentile(img.ravel(), stretch_percentiles[1])/255 - img[img > 255] = 255 - img = np.round(img).astype(np.uint8) - - hls = cv2.cvtColor(img, cv2.COLOR_RGB2HLS) - - clahe = cv2.createCLAHE(clipLimit=clip_limit, tileGridSize=(16, 16)) - hls[:, :, 1] = clahe.apply(hls[:, :, 1]) - - img = cv2.cvtColor(hls, cv2.COLOR_HLS2RGB) - return img - -cm_rgb = load_from_file(rgb_camera_model_fname) -cm_ir = load_from_file(ir_camera_model_fname) - -rgb_fnames = glob.glob('%s/*.jpg' % rgb_img_dir) -random.shuffle(rgb_fnames) -k = 0 - - -for rgb_fname in rgb_fnames[:num_gifs]: - if k == num_gifs: - break - - ir_fname = '%s/%sir.tif' % (ir_img_dir, os.path.split(rgb_fname[:-7])[1]) - img2 = cv2.imread(ir_fname, cv2.IMREAD_COLOR) - - if img2 is None: - continue - - img1 = cv2.imread(rgb_fname, cv2.IMREAD_COLOR) - - if img1 is None: - continue - - k += 1 - - img1 = img1[:, :, ::-1] - img2 = img2[:, :, ::-1] - - img1 = stretch_contrast(img1, clip_limit=3, stretch_percentiles=[0.1, 99.9]) - img2 = stretch_contrast(img2, clip_limit=3, stretch_percentiles=[0.1, 99.9]) - - img3, mask = render_view(cm_rgb, img1, 0, cm_ir, 0, block_size=10) - - img2_ = PIL.Image.fromarray(img2) - img3_ = PIL.Image.fromarray(img3) - fname_out = '%s/registration_%i.gif' % (out_dir, k+1) - img2_.save(fname_out, save_all=True, append_images=[img3_], - duration=250, loop=0) diff --git a/kamera/postflight/scripts/camera_calibration.py b/kamera/postflight/scripts/camera_calibration.py deleted file mode 100644 index 8917548a..00000000 --- a/kamera/postflight/scripts/camera_calibration.py +++ /dev/null @@ -1,1213 +0,0 @@ -#!/usr/bin/env python -import os -import os.path as osp -import json -import cv2 -import PIL -import pathlib -import numpy as np -import matplotlib.pyplot as plt -import ubelt as ub -from random import shuffle -from scipy.optimize import minimize, fminbound -from matplotlib.backends.backend_pdf import PdfPages - -# Custom package imports. -from kamera.sensor_models import ( - quaternion_multiply, - quaternion_from_matrix, - quaternion_inverse, - ) -from kamera.sensor_models.nav_conversions import enu_to_llh -from kamera.sensor_models.nav_state import NavStateINSJson, NavStateFixed -from kamera.colmap_processing.camera_models import StandardCamera -from kamera.colmap_processing.colmap_interface import ( - read_images_binary, - read_points3D_binary, - read_cameras_binary, - qvec2rotmat, - ) -from kamera.colmap_processing.image_renderer import render_view - - -def get_base_name(fname): - """ Given an arbitrary filename (could be UV, IR, RGB, json), - extract the portion of the filename that is just the time, flight, - machine (C, L, R), and effort name. - """ - # get base - base = osp.basename(fname) - # get it without an extension and modality - modality_agnostic = "_".join(base.split("_")[:-1]) - return modality_agnostic - - -def get_modality(fname): - base = osp.basename(fname) - modality = base.split("_")[-1].split('.')[0] - return modality - - -def get_channel(fname): - base = osp.basename(fname) - channel = base.split("_")[3] - return channel - - -def get_basename_to_time(flight_dir) -> dict: - # Establish correspondence between real-world exposure times base of file - # names. - basename_to_time = {} - for json_fname in pathlib.Path(flight_dir).rglob('*_meta.json'): - try: - with open(json_fname) as json_file: - d = json.load(json_file) - # Time that the image was taken. - basename = get_base_name(json_fname) - basename_to_time[basename] = float(d['evt']['time']) - except (OSError, IOError): - pass - return basename_to_time - - -def process_images(colmap_images, basename_to_time, nav_state_provider): - """ - - Returns: - :param img_fnames: Image filename associated with each of the images in - 'colmap_images'. - :type img_fnames: list of str - - :param img_times: INS-reported time associated with the trigger of each - image in 'colmap_images'. - :type img_times: - - :param ins_poses: INS-reported pose, (x, y, z) position and (x, y, z, w) - quaternion, associated with the trigger of time of each image in - 'colmap_images'. - :type ins_poses: - - :param sfm_poses: Colmap-reported reported pose, (x, y, z) position and - (x, y, z, w) quaternion, associated with the trigger time of each image - in 'colmap_images'. - :type sfm_poses: - - """ - img_fnames = [] - img_times = [] - ins_poses = [] - sfm_poses = [] - llhs = [] - for image_num in colmap_images: - image = colmap_images[image_num] - base_name = get_base_name(image.name) - try: - t = basename_to_time[base_name] - - # Query the navigation state recorded by the INS for this time. - pose = nav_state_provider.pose(t) - llh = nav_state_provider.llh(t) - - # Query Colmaps pose for the camera. - R = qvec2rotmat(image.qvec) - pos = -np.dot(R.T, image.tvec) - - # The qvec used by Colmap is a (w, x, y, z) quaternion - # representing the rotation of a vector defined in the world - # coordinate system into the camera coordinate system. However, - # the 'camera_models' module assumes (x, y, z, w) quaternions - # representing a coordinate system rotation. Also, the quaternion - # used by 'camera_models' represents a coordinate system rotation - # versus the coordinate system transform of Colmap's convention, - # so we need an inverse. - - #quat = transformations.quaternion_inverse(image.qvec) - quat = image.qvec / np.linalg.norm(image.qvec) - quat[0] = -quat[0] - - quat = [quat[1], quat[2], quat[3], quat[0]] - - sfm_pose = [pos, quat] - - img_times.append(t) - ins_poses.append(pose) - img_fnames.append(image.name) - sfm_poses.append(sfm_pose) - llhs.append(llh) - except KeyError: - print('Couldn\'t find a _meta.json file associated with \'%s\'' % - base_name) - - ind = np.argsort(img_fnames) - img_fnames = [img_fnames[i] for i in ind] - img_times = [img_times[i] for i in ind] - ins_poses = [ins_poses[i] for i in ind] - sfm_poses = [sfm_poses[i] for i in ind] - llhs = [llhs[i] for i in ind] - - return img_fnames, img_times, ins_poses, sfm_poses, llhs - - -def write_image_locations(locations_fname, img_fnames, ins_poses): - with open(locations_fname, 'w') as fo: - for i in range(len(img_fnames)): - name = img_fnames[i] - pos = ins_poses[i][0] - fo.write('%s %0.8f %0.8f %0.8f\n' % (name, pos[0], pos[1], pos[2])) - - -def get_colmap_data(colmap_images, colmap_cameras, - points3d, basename_to_time) -> tuple: - # Load in all of the Colmap results into more-convenient structures. - points_per_image = {} - camera_from_camera_str = {} - for image_num in colmap_images: - image = colmap_images[image_num] - camera_str = osp.basename(osp.dirname(image.name)) - camera_from_camera_str[camera_str] = colmap_cameras[image.camera_id] - - xys = image.xys - pt_ids = image.point3D_ids - ind = pt_ids != -1 - pt_ids = pt_ids[ind] - xys = xys[ind] - xyzs = np.array([points3d[pt_id].xyz for pt_id in pt_ids]) - base_name = get_base_name(image.name) - try: - t = basename_to_time[base_name] - points_per_image[image.name] = (xys, xyzs, t) - except KeyError: - pass - return points_per_image, camera_from_camera_str - - -def perform_error_analysis(camera_model, points_per_image_, save_dir, camera_str): - """ - Perform error analysis and save all plots into a single PDF. - - Parameters: - - camera_model: The calibrated camera model. - - points_per_image_: List of tuples containing image points, corresponding 3D points, and timestamp. - - save_dir: Directory where the PDF will be saved. - - camera_str: String identifier for the camera (used in PDF filename). - """ - err_meters = [] - err_pixels = [] - err_pixels_per_frame = [] - err_angle = [] - ifov = np.mean(camera_model.ifov()) # Assuming 'ifov' stands for 'instantaneous field of view' - - for xys, xyzs, t in points_per_image_: - # Project 3D points to 2D image points - xys2 = camera_model.project(xyzs.T, t) - err_pixels_ = np.sqrt(np.sum((xys2 - xys.T)**2, axis=0)) - err_pixels_per_frame.append([t, err_pixels_.mean()]) - err_pixels.extend(err_pixels_.tolist()) - - # Unproject image points to camera rays - ray_pos, ray_dir = camera_model.unproject(xys.T, t) - - # Compute direction from camera to 3D points - ray_dir2 = xyzs.T - ray_pos - dist = np.linalg.norm(ray_dir2, axis=0) - ray_dir2 /= dist - - # Calculate angular deviation - dp = np.clip(np.sum(ray_dir * ray_dir2, axis=0), -1, 1) - theta = np.arccos(dp) - err_angle.extend(theta.tolist()) - - # Calculate orthogonal distance in meters - err_meters.extend((np.sin(theta) * dist).tolist()) - - # Convert lists to numpy arrays for easier manipulation - err_meters = np.array(err_meters) - err_pixels = np.array(err_pixels) - err_angle = np.array(err_angle) - err_pixels_per_frame = np.array(err_pixels_per_frame).T - - # Sort the errors - sorted_err_meters = np.sort(err_meters) - sorted_err_pixels = np.sort(err_pixels) - sorted_err_angle = np.sort(err_angle) - - # Initialize PdfPages object - pdf_filename = f"{save_dir}/error_analysis_{camera_str}.pdf" - with PdfPages(pdf_filename) as pdf: - # --- Plot 1: Histogram of Pixel Errors --- - plt.figure(figsize=(8, 6)) - plt.hist(sorted_err_pixels, bins=50, color='blue', alpha=0.7) - plt.title('Pixel Errors') - plt.xlabel('Error (pixels)') - plt.ylabel('Frequency') - plt.grid(True) - pdf.savefig() # Save the current figure into the PDF - plt.close() - - # --- Plot 2: Histogram of Meter Errors --- - plt.figure(figsize=(8, 6)) - plt.hist(sorted_err_meters, bins=50, color='green', alpha=0.7) - plt.title('Meter Errors') - plt.xlabel('Error (meters)') - plt.ylabel('Frequency') - plt.grid(True) - pdf.savefig() - plt.close() - - # --- Plot 3: Histogram of Angular Errors --- - plt.figure(figsize=(8, 6)) - plt.hist(np.degrees(sorted_err_angle), bins=50, color='red', alpha=0.7) - plt.title('Angular Errors') - plt.xlabel('Error (degrees)') - plt.ylabel('Frequency') - plt.grid(True) - pdf.savefig() - plt.close() - - # --- Plot 4: Pixel Errors per Frame --- - plt.figure(figsize=(10, 6)) - plt.plot(err_pixels_per_frame[0], err_pixels_per_frame[1], marker='o', linestyle='-', color='purple') - plt.title('Average Pixel Error per Frame') - plt.xlabel('Frame Index or Timestamp') - plt.ylabel('Average Pixel Error (pixels)') - plt.grid(True) - pdf.savefig() - plt.close() - - # --- Optional: Additional Plots --- - # If you have more plots to include, add them here following the same pattern. - - print(f"Error analysis plots have been saved to {pdf_filename}") - - -def calibrate_rgb(rgb_camera_strs, img_fnames, ins_poses, sfm_poses, - points_per_image, camera_from_camera_str, - nav_state_provider, save_dir): - for camera_str in rgb_camera_strs: - ins_quat_ = [] - sfm_quat_ = [] - points_per_image_ = [] - for i in range(len(img_fnames)): - fname = img_fnames[i] - if osp.basename(osp.dirname(fname)) == camera_str: - ins_quat_.append(ins_poses[i][1]) - sfm_quat_.append(sfm_poses[i][1]) - points_per_image_.append(points_per_image[fname]) - - # Both quaternions are of the form (x, y, z, w) and represent a coordinate - # system rotation. - #q_sfm = quaternion_inverse(q_cam)*quaternion_inverse(q_ins) - cam_quats = [quaternion_inverse(quaternion_multiply(sfm_quat_[k], - ins_quat_[k])) - for k in range(len(ins_quat_))] - - colmap_camera = camera_from_camera_str[camera_str] - - if colmap_camera.model == 'OPENCV': - fx, fy, cx, cy, d1, d2, d3, d4 = colmap_camera.params - elif colmap_camera.model == 'PINHOLE': - fx, fy, cx, cy = colmap_camera.params - d1 = d2 = d3 = d4 = 0 - - K = K = np.array([[fx, 0, cx], [0, fy, cy], [0, 0, 1]]) - dist = np.array([d1, d2, d3, d4]) - - def cam_quat_error(cam_quat) -> float: - cam_quat = cam_quat/np.linalg.norm(cam_quat) - camera_model = StandardCamera(colmap_camera.width, - colmap_camera.height, - K, dist, [0, 0, 0], cam_quat, - platform_pose_provider=nav_state_provider) - - err = [] - for xys, xyzs, t in points_per_image_: - if False: - # Reprojection error. - xys2 = camera_model.project(xyzs.T, t) - err_ = np.sqrt(np.sum((xys2 - xys.T)**2, axis=0)) - else: - # Error in meters. - - # Rays coming out of the camera in the direction of the imaged points. - ray_pos, ray_dir = camera_model.unproject(xys.T, t) - - # Direction coming out of the camera pointing at the actual 3-D points' - # locatinos. - ray_dir2 = xyzs.T - ray_pos - d = np.sqrt(np.sum((ray_dir2)**2, axis=0)) - ray_dir2 /= d - - dp = np.minimum(np.sum(ray_dir*ray_dir2, axis=0), 1) - dp = np.maximum(dp, -1) - theta = np.arccos(dp) - err_ = np.sin(theta)*d - #err.append(np.percentile(err_, 90)) - err.append(np.mean(err_)) - - err = np.array(err) - #err = err[err < np.percentile(err, 90)] - - err = np.mean(err) - #print('RMS reproject error for quat', cam_quat, ': %0.8f' % err) - return err - - print("Iterating through %s quaternion guesses." % len(cam_quats)) - shuffle(cam_quats) - best_quat = None - best_err = np.inf - for i in range(len(cam_quats)): - if True: - cam_quat = cam_quats[i] - else: - cam_quat = np.random.rand(4)*2-1 - - err = cam_quat_error(cam_quat) - if err < best_err: - best_err = err - best_quat = cam_quat - - if best_err < 10: - break - - print("Best error: ", best_err) - print("Best quat: ") - print(cam_quat) - - print("Minimizing error over camera quaternions") - - ret = minimize(cam_quat_error, best_quat) - best_quat = ret.x/np.linalg.norm(ret.x) - ret = minimize(cam_quat_error, best_quat, method='BFGS') - best_quat = ret.x/np.linalg.norm(ret.x) - ret = minimize(cam_quat_error, best_quat, method='Powell') - best_quat = ret.x/np.linalg.norm(ret.x) - - # Sequential 1-D optimizations. - for i in range(4): - def set_x(x): - quat = best_quat.copy() - quat = quat/np.linalg.norm(quat) - while abs(quat[i] - x) > 1e-6: - quat[i] = x - quat = quat/np.linalg.norm(quat) - - return quat - - def func(x): - return cam_quat_error(set_x(x)) - - x = np.linspace(-1, 1, 100); x = sorted(np.hstack([x, best_quat[i]])) - y = [func(x_) for x_ in x] - x = fminbound(func, x[np.argmin(y) - 1], x[np.argmin(y) + 1], xtol=1e-8) - best_quat = set_x(x) - - camera_model = StandardCamera(colmap_camera.width, colmap_camera.height, - K, dist, [0, 0, 0], best_quat, - platform_pose_provider=nav_state_provider) - - ub.ensuredir(save_dir) - - camera_model.save_to_file('%s/%s.yaml' % (save_dir, camera_str)) - - perform_error_analysis(camera_model, - points_per_image_, - save_dir, - camera_str) - - -def create_time_modality_mapping(colmap_images, basename_to_time): - print("Creating mapping between RGB and UV images...") - time_to_modality = ub.AutoDict() - for image in colmap_images.values(): - base_name = get_base_name(image.name) - try: - t = basename_to_time[base_name] - except Exception as e: - print(e) - print(f"No ins time found for image {base_name}.") - continue - modality = get_modality(image.name) - time_to_modality[t][modality] = image - return time_to_modality - - -def create_fname_to_time_channel_modality(img_fnames, basename_to_time): - print("Creating mapping between RGB and UV images...") - time_to_modality = ub.AutoDict() - for fname in img_fnames: - base_name = get_base_name(fname) - try: - t = basename_to_time[base_name] - except Exception as e: - print(e) - print(f"No ins time found for image {base_name}.") - continue - modality = get_modality(fname) - channel = get_channel(fname) - time_to_modality[t][channel][modality] = fname - return time_to_modality - - -def write_gifs(gif_dir, colmap_dir, img_fnames, - fname_to_time_channel_modality, - basename_to_time, rgb_str, camera_str, - cm_rgb, cm_uv): - print(f"Writing a registration gif for cameras {rgb_str} " - f"and {camera_str}.") - # Pick an image pair and register. - ub.ensuredir(gif_dir) - - for k in range(10): - inds = list(range(len(img_fnames))) - shuffle(inds) - for i in range(len(img_fnames)): - uv_img = rgb_img = None - fname1 = img_fnames[inds[i]] - if osp.basename(osp.dirname(fname1)) != rgb_str: - continue - t1 = basename_to_time[get_base_name(fname1)] - channel = get_channel(fname1) # L/C/R - try: - rgb_fname = fname_to_time_channel_modality[t1][channel]["rgb"] - abs_rgb_fname = os.path.join(colmap_dir, 'images0', rgb_fname) - rgb_img = cv2.imread(abs_rgb_fname, cv2.IMREAD_COLOR)[:, :, ::-1] - except Exception as e: - print(f"No rgb image found at time {t1}") - continue - try: - uv_fname = fname_to_time_channel_modality[t1][channel]["uv"] - abs_uv_fname = os.path.join(colmap_dir, 'images0', uv_fname) - uv_img = cv2.imread(abs_uv_fname, cv2.IMREAD_COLOR)[:, :, ::-1] - break - except Exception as e: - print(f"No uv image found at time {t1}") - continue - - if uv_img is None or rgb_img is None: - print("Failed to find matching image pair, skipping.") - continue - print(f"Writing {rgb_fname} and {uv_fname} to gif.") - - # Warps the color image img1 into the uv camera model cm_uv - warped_rgb_img, mask = render_view(cm_rgb, rgb_img, 0, - cm_uv, 0, block_size=10) - - ds_warped_rgb_img = PIL.Image.fromarray(cv2.pyrDown( - cv2.pyrDown(cv2.pyrDown(warped_rgb_img)))) - ds_uv_img = PIL.Image.fromarray(cv2.pyrDown( - cv2.pyrDown(cv2.pyrDown(uv_img)))) - fname_out = osp.join(gif_dir, - f"{rgb_str}_to_{camera_str}_registration_{k+1}.gif") - print(f"Writing gif to {fname_out}.") - ds_uv_img.save(fname_out, save_all=True, - append_images=[ds_warped_rgb_img], - duration=350, loop=0) - - -def calibrate_uv(uv_camera_strs, img_fnames, colmap_images, - camera_from_camera_str, save_dir, - basename_to_time, time_to_modality, - fname_to_time_channel_modality, - colmap_dir, points_per_image): - nav_state_fixed = NavStateFixed(np.zeros(3), [0, 0, 0, 1]) - skipped = 0 - total = 0 - for uv_str in uv_camera_strs: - print(f"Matching images to camera {uv_str}.") - rgb_str = uv_str.replace('uv', 'rgb') - cm_rgb = StandardCamera.load_from_file(osp.join(save_dir, rgb_str + '.yaml'), - platform_pose_provider=nav_state_fixed) - im_pts_uv = [] - im_pts_rgb = [] - - # Build up pairs of image coordinates between the two cameras from image - # pairs acquired from the same time. - image_nums = sorted(list(colmap_images.keys())) - for image_num in image_nums: - #print('%i/%i' % (image_num + 1, image_nums[-1])) - image = colmap_images[image_num] - im_str = osp.basename(osp.dirname(image.name)) - if im_str != uv_str: - #print(f"{im_str} does not match {camera_str}, skipping.") - continue - - # now we know it's uv - image_uv = image - base_name = get_base_name(image_uv.name) - - try: - t1 = basename_to_time[base_name] - except KeyError: - print(f"No time found for {base_name}.") - continue - - try: - image_rgb = time_to_modality[t1]["rgb"] - except KeyError: - print(f"No rgb image found at {t1}.") - continue - - # Both 'uv_image' and 'image_rgb' are from the same time. - pt_ids1 = image_uv.point3D_ids - ind = pt_ids1 != -1 - xys1 = dict(zip(pt_ids1[ind], image_uv.xys[ind])) - - pt_ids2 = image_rgb.point3D_ids - ind = pt_ids2 != -1 - xys2 = dict(zip(pt_ids2[ind], image_rgb.xys[ind])) - - match_ids = set(xys1.keys()).intersection(set(xys2.keys())) - total += 1 - if len(match_ids) < 1: - #print("No match IDs found.") - skipped += 1 - continue - - for match_id in match_ids: - im_pts_uv.append(xys1[match_id]) - im_pts_rgb.append(xys2[match_id]) - - print(f"Matched {total-skipped}/{total} image pairs, resulting in " - f"{len(im_pts_uv)} matching UV and RGB points.") - - im_pts_uv = np.array(im_pts_uv) - im_pts_rgb = np.array(im_pts_rgb) - # Arbitrary cut off - minimum_pts_required = 10 - if len(im_pts_rgb) < minimum_pts_required or \ - len(im_pts_uv) < minimum_pts_required: - print("[ERROR] Not enough matching RGB/UV image points were found " - f"for camera {uv_str}.") - continue - - if False: - plt.subplot(121) - plt.plot(im_pts_uv[:, 0], im_pts_uv[:, 1], 'ro') - plt.subplot(122) - plt.plot(im_pts_rgb[:, 0], im_pts_rgb[:, 1], 'bo') - - # Treat as co-located cameras (they are) and unproject out of RGB and into - # the other camera. - ray_pos, ray_dir = cm_rgb.unproject(im_pts_rgb.T) - wrld_pts = ray_dir.T*1e4 - assert np.all(np.isfinite(wrld_pts)), "World points contain non-finite values." - - colmap_camera = camera_from_camera_str[uv_str] - - if colmap_camera.model == 'OPENCV': - fx, fy, cx, cy, d1, d2, d3, d4 = colmap_camera.params - elif colmap_camera.model == 'PINHOLE': - fx, fy, cx, cy = colmap_camera.params - d1 = d2 = d3 = d4 = 0 - - K = np.array([[fx, 0, cx], [0, fy, cy], [0, 0, 1]]) - dist = np.array([d1, d2, d3, d4], dtype=np.float32) - - flags = cv2.CALIB_ZERO_TANGENT_DIST - flags = flags | cv2.CALIB_USE_INTRINSIC_GUESS - flags = flags | cv2.CALIB_FIX_PRINCIPAL_POINT - flags = flags | cv2.CALIB_FIX_K1 - flags = flags | cv2.CALIB_FIX_K2 - flags = flags | cv2.CALIB_FIX_K3 - flags = flags | cv2.CALIB_FIX_K4 - flags = flags | cv2.CALIB_FIX_K5 - flags = flags | cv2.CALIB_FIX_K6 - - criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 30000, - 0.0000001) - - ret = cv2.calibrateCamera([wrld_pts.astype(np.float32)], - [im_pts_uv.astype(np.float32)], - (colmap_camera.width, colmap_camera.height), - cameraMatrix=K.copy(), distCoeffs=dist.copy(), - flags=flags, criteria=criteria) - - err, _, _, rvecs, tvecs = ret - - R = np.identity(4) - R[:3, :3] = cv2.Rodrigues(rvecs[0])[0] - cam_quat = quaternion_from_matrix(R.T) - - # Only optimize 3/4 components of the quaternion. - static_quat_ind = np.argmax(np.abs(cam_quat)) - dynamic_quat_ind = [ i for i in range(4) if i != static_quat_ind ] - #static_quat_ind = 3 # Fixing the 'w' component - #dynamic_quat_ind = [0, 1, 2] # Optimizing 'x', 'y', 'z' components - dynamic_quat_ind = np.array(dynamic_quat_ind) - cam_quat = np.asarray(cam_quat) - cam_quat /= np.linalg.norm(cam_quat) - x0 = cam_quat[dynamic_quat_ind].copy() # [x, y, z] - - def get_cm(x): - """ - Create a camera model with updated quaternion and intrinsic parameters. - - Parameters: - - x: array-like, shape (N,) - Optimization variables where the first 3 elements correspond to - the dynamic quaternion components ('x', 'y', 'z'), optionally - followed by intrinsic parameters ('fx', 'fy', etc.). - - Returns: - - cm: StandardCamera instance - Updated camera model with new parameters. - """ - # Ensure 'x' has at least 3 elements for quaternion - assert len(x) > 2, "Optimization variable 'x' must have at least 3 elements for quaternion." - - # Validate 'x[:3]' are finite numbers - assert np.all(np.isfinite(x[:3])), "Quaternion components contain non-finite values." - - # Initialize quaternion with fixed 'w' component - cam_quat_new = np.ones(4) - - # Assign dynamic components from optimization variables - cam_quat_new[dynamic_quat_ind] = x[:3] - - # Normalize to ensure it's a unit quaternion - norm = np.linalg.norm(cam_quat_new) - assert norm > 1e-6, "Quaternion has zero or near-zero magnitude." - cam_quat_new /= norm - - # Extract intrinsic parameters - if len(x) > 3: - fx_ = x[3] - fy_ = x[4] - else: - fx_ = fx - fy_ = fy - - if len(x) > 5: - dist_ = x[5:] - else: - dist_ = dist - - # Construct the intrinsic matrix - K = np.array([[fx_, 0, cx], [0, fy_, cy], [0, 0, 1]]) - - # Create the camera model - cm = StandardCamera( - colmap_camera.width, - colmap_camera.height, - K, - dist_, - [0, 0, 0], - cam_quat_new, - platform_pose_provider=nav_state_fixed - ) - return cm - - def error(x): - try: - cm = get_cm(x) - projected_uv = cm.project(wrld_pts.T).T # Shape: (N, 2) - - # Compute Euclidean distances - err = np.sqrt(np.sum((im_pts_uv - projected_uv) ** 2, axis=1)) - - # Apply Huber loss - delta = 20 - ind = err < delta - err[ind] = err[ind] ** 2 - err[~ind] = 2 * (err[~ind] - delta / 2) * delta - - # Sort and trim the error - err = sorted(err)[:len(err) - len(err) // 5] - - # Compute mean error - mean_err = np.sqrt(np.mean(err)) - - # Add regularization term (e.g., L2 penalty) - reg_strength = 1e-3 # Adjust as needed - reg_term = reg_strength * np.linalg.norm(x[:3])**2 - - total_error = mean_err + reg_term - return total_error - except Exception as e: - print(f"Error in error function: {e}") - return np.inf # Assign a high error if computation fails - - # Optional: Define a callback function to monitor optimization - def callback(xk): - try: - cm = get_cm(xk) - projected_uv = cm.project(wrld_pts.T).T - err = np.sqrt(np.sum((im_pts_uv - projected_uv) ** 2, axis=1)) - mean_err = np.mean(err) - print(f"Current x: {xk}, Mean Error: {mean_err}") - except Exception as e: - print(f"Error in callback: {e}") - - def plot_results1(x): - cm = get_cm(x) - err = np.sqrt(np.sum((im_pts_uv - cm.project(wrld_pts.T).T)**2, 1)) - err = sorted(err) - plt.plot(np.linspace(0, 100, len(err)), err) - - print("Optimizing error for UV models.") - x = x0.copy() - # Example bounds for [x, y, z] components - bounds = [(-1.0, 1.0), # x - (-1.0, 1.0), # y - (-1.0, 1.0)] # z - print("First pass") - # Perform optimization on [x, y, z] - ret = minimize( - error, - x, - method='L-BFGS-B', - bounds=bounds, - callback=None, # Optional: Monitor progress - options={'disp': False, 'maxiter': 30000, 'ftol': 1e-7} - ) - assert ret.success, "Minimization of UV error failed." - x = np.hstack([ret.x, fx, fy]) - print("Second pass") - assert np.all(np.isfinite(x)), "Input quaternion with locked fx, fy, is not finite." - ret = minimize(error, x, method='Powell') - x = ret.x - print("Third pass") - assert np.all(np.isfinite(x)), "Input quaternion for BFGS is not finite." - ret = minimize(error, x, method='BFGS') - - print("Final pass") - if True: - x = np.hstack([ret.x, dist]) - ret = minimize(error, x, method='Powell'); x = ret.x - ret = minimize(error, x, method='BFGS'); x = ret.x - - assert np.all(np.isfinite(x)), "Input quaternion for final model is not finite." - cm_uv = get_cm(x) - cm_uv.save_to_file('%s/%s.yaml' % (save_dir, uv_str)) - - perform_error_analysis_and_save_pdf(cm_uv, cm_rgb, points_per_image, - save_dir, - uv_str, rgb_str) - gif_dir = osp.join(save_dir, 'registration_gifs') - write_gifs(gif_dir, colmap_dir, img_fnames, - fname_to_time_channel_modality, - basename_to_time, rgb_str, uv_str, - cm_rgb, cm_uv) - - -def perform_error_analysis_and_save_pdf(camera_model_uv, camera_model_rgb, - points_per_image, - save_dir, - uv_str, - rgb_str): - """ - Perform error analysis for both UV and RGB models and save all plots into a single PDF. - - Parameters: - - camera_model_uv: Calibrated UV camera model. - - camera_model_rgb: Calibrated RGB camera model. - - points_per_image_uv: List of tuples containing (image points, corresponding 3D points, timestamp) for UV. - - points_per_image_rgb: List of tuples containing (image points, corresponding 3D points, timestamp) for RGB. - - save_dir: Directory where the PDF will be saved. - - uv_str: String identifier for the UV camera. - - rgb_str: String identifier for the RGB camera. - """ - - # Initialize error lists for UV - err_meters_uv = [] - err_pixels_uv = [] - err_pixels_per_frame_uv = [] - err_angle_uv = [] - im_pts_uv = [] - im_pts_rgb = [] - - # Mean IFOV for UV (assuming similar to RGB) - ifov_uv = np.mean(camera_model_uv.ifov()) - - #import ipdb; ipdb.set_trace() - # Error analysis for UV - for fname, (xys, xyzs, t) in points_per_image.items(): - im_str = osp.basename(osp.dirname(fname)) - if im_str != uv_str: - continue - im_pts_uv.extend(xys) - # Project 3D points to 2D image points using UV model - xys2 = camera_model_uv.project(xyzs.T, t) - err_pixels_ = np.sqrt(np.sum((xys2 - xys.T)**2, axis=0)) - err_pixels_per_frame_uv.append([t, err_pixels_.mean()]) - err_pixels_uv.extend(err_pixels_.tolist()) - - # Unproject image points to camera rays - ray_pos, ray_dir = camera_model_uv.unproject(xys.T, t) - - # Compute direction from camera to 3D points - ray_dir2 = xyzs.T - ray_pos - dist = np.linalg.norm(ray_dir2, axis=0) - ray_dir2 /= dist - - # Calculate angular deviation - dp = np.clip(np.sum(ray_dir * ray_dir2, axis=0), -1, 1) - theta = np.arccos(dp) - err_angle_uv.extend(theta.tolist()) - - # Calculate orthogonal distance in meters - err_meters_uv.extend((np.sin(theta) * dist).tolist()) - - # Initialize error lists for RGB - err_meters_rgb = [] - err_pixels_rgb = [] - err_pixels_per_frame_rgb = [] - err_angle_rgb = [] - - # Mean IFOV for RGB - ifov_rgb = np.mean(camera_model_rgb.ifov()) - - # Error analysis for RGB - for fname, (xys, xyzs, t) in points_per_image.items(): - im_str = osp.basename(osp.dirname(fname)) - if im_str != rgb_str: - continue - im_pts_rgb.extend(xys) - # Project 3D points to 2D image points using RGB model - xys2 = camera_model_rgb.project(xyzs.T, t) - err_pixels_ = np.sqrt(np.sum((xys2 - xys.T)**2, axis=0)) - err_pixels_per_frame_rgb.append([t, err_pixels_.mean()]) - err_pixels_rgb.extend(err_pixels_.tolist()) - - # Unproject image points to camera rays - ray_pos, ray_dir = camera_model_rgb.unproject(xys.T, t) - - # Compute direction from camera to 3D points - ray_dir2 = xyzs.T - ray_pos - dist = np.linalg.norm(ray_dir2, axis=0) - ray_dir2 /= dist - - # Calculate angular deviation - dp = np.clip(np.sum(ray_dir * ray_dir2, axis=0), -1, 1) - theta = np.arccos(dp) - err_angle_rgb.extend(theta.tolist()) - - # Calculate orthogonal distance in meters - err_meters_rgb.extend((np.sin(theta) * dist).tolist()) - - # Convert lists to numpy arrays for easier manipulation - err_meters_uv = np.array(err_meters_uv) - err_pixels_uv = np.array(err_pixels_uv) - err_angle_uv = np.array(err_angle_uv) - err_pixels_per_frame_uv = np.array(err_pixels_per_frame_uv).T - - err_meters_rgb = np.array(err_meters_rgb) - err_pixels_rgb = np.array(err_pixels_rgb) - err_angle_rgb = np.array(err_angle_rgb) - err_pixels_per_frame_rgb = np.array(err_pixels_per_frame_rgb).T - - # Sort the errors - sorted_err_meters_uv = np.sort(err_meters_uv) - sorted_err_pixels_uv = np.sort(err_pixels_uv) - sorted_err_angle_uv = np.sort(err_angle_uv) - - sorted_err_meters_rgb = np.sort(err_meters_rgb) - sorted_err_pixels_rgb = np.sort(err_pixels_rgb) - sorted_err_angle_rgb = np.sort(err_angle_rgb) - - im_pts_uv = np.asarray(im_pts_uv) - im_pts_rgb = np.asarray(im_pts_rgb) - - # Initialize PdfPages object - pdf_filename = f"{save_dir}/error_analysis_{uv_str}_{rgb_str}.pdf" - with PdfPages(pdf_filename) as pdf: - # --- Plot 1: Summary Statistics for UV --- - plt.figure(figsize=(11.69, 8.27)) # A4 size in inches - plt.axis('off') # Hide axes - - summary_text_uv = f""" - Error Analysis Summary for UV Camera: {uv_str} - - Pixel Errors: - - Mean: {np.mean(err_pixels_uv):.2f} pixels - - Median: {np.median(err_pixels_uv):.2f} pixels - - Max: {np.max(err_pixels_uv):.2f} pixels - - Meter Errors: - - Mean: {np.mean(err_meters_uv):.2f} meters - - Median: {np.median(err_meters_uv):.2f} meters - - Max: {np.max(err_meters_uv):.2f} meters - - Angular Errors: - - Mean: {np.degrees(np.mean(err_angle_uv)):.2f} degrees - - Median: {np.degrees(np.median(err_angle_uv)):.2f} degrees - - Max: {np.degrees(np.max(err_angle_uv)):.2f} degrees - """ - - plt.text(0.5, 0.5, summary_text_uv, fontsize=20, ha='center', va='center', wrap=True) - plt.title('Error Analysis Summary for UV Camera', fontsize=24) - pdf.savefig() - plt.close() - - # --- Plot 2: Histogram of Pixel Errors for UV --- - plt.figure(figsize=(11.69, 8.27)) - plt.hist(sorted_err_pixels_uv, bins=50, color='blue', alpha=0.7) - plt.title('UV Camera - Pixel Errors', fontsize=24) - plt.xlabel('Error (pixels)', fontsize=20) - plt.ylabel('Frequency', fontsize=20) - plt.grid(True) - pdf.savefig() - plt.close() - - # --- Plot 3: Histogram of Meter Errors for UV --- - plt.figure(figsize=(11.69, 8.27)) - plt.hist(sorted_err_meters_uv, bins=50, color='green', alpha=0.7) - plt.title('UV Camera - Meter Errors', fontsize=24) - plt.xlabel('Error (meters)', fontsize=20) - plt.ylabel('Frequency', fontsize=20) - plt.grid(True) - pdf.savefig() - plt.close() - - # --- Plot 4: Histogram of Angular Errors for UV --- - plt.figure(figsize=(11.69, 8.27)) - plt.hist(np.degrees(sorted_err_angle_uv), bins=50, color='red', alpha=0.7) - plt.title('UV Camera - Angular Errors', fontsize=24) - plt.xlabel('Error (degrees)', fontsize=20) - plt.ylabel('Frequency', fontsize=20) - plt.grid(True) - pdf.savefig() - plt.close() - - # --- Plot 5: Average Pixel Error per Frame for UV --- - plt.figure(figsize=(11.69, 8.27)) - plt.plot(err_pixels_per_frame_uv[0], err_pixels_per_frame_uv[1], marker='o', linestyle='-', color='purple') - plt.title('UV Camera - Average Pixel Error per Frame', fontsize=24) - plt.xlabel('Frame Index or Timestamp', fontsize=20) - plt.ylabel('Average Pixel Error (pixels)', fontsize=20) - plt.grid(True) - pdf.savefig() - plt.close() - - # --- Plot 6: Scatter Plot of UV and RGB Points --- - plt.figure(figsize=(11.69, 8.27)) - plt.subplot(1, 2, 1) - plt.scatter(im_pts_uv[:, 0], im_pts_uv[:, 1], c='blue', marker='o', alpha=0.5, label='UV Observed') - plt.title('UV Camera - Observed UV Points', fontsize=24) - plt.xlabel('U', fontsize=20) - plt.ylabel('V', fontsize=20) - plt.legend() - plt.grid(True) - - plt.subplot(1, 2, 2) - plt.scatter(im_pts_rgb[:, 0], im_pts_rgb[:, 1], c='green', marker='x', alpha=0.5, label='RGB Observed') - plt.title('RGB Camera - Observed RGB Points', fontsize=24) - plt.xlabel('R', fontsize=20) - plt.ylabel('G', fontsize=20) - plt.legend() - plt.grid(True) - - plt.suptitle('Scatter Plots of Observed Points', fontsize=28) - plt.tight_layout(rect=[0, 0.03, 1, 0.95]) - pdf.savefig() - plt.close() - - # --- Plot 7: Reprojection Error Histograms for Both UV and RGB --- - plt.figure(figsize=(11.69, 8.27)) - plt.subplot(1, 2, 1) - plt.hist(sorted_err_pixels_uv, bins=50, color='blue', alpha=0.7, label='UV Pixel Errors') - plt.hist(sorted_err_pixels_rgb, bins=50, color='red', alpha=0.5, label='RGB Pixel Errors') - plt.title('Reprojection Pixel Errors', fontsize=24) - plt.xlabel('Error (pixels)', fontsize=20) - plt.ylabel('Frequency', fontsize=20) - plt.legend() - plt.grid(True) - - plt.subplot(1, 2, 2) - plt.hist(np.degrees(sorted_err_angle_uv), bins=50, color='blue', alpha=0.7, label='UV Angular Errors') - plt.hist(np.degrees(sorted_err_angle_rgb), bins=50, color='red', alpha=0.5, label='RGB Angular Errors') - plt.title('Reprojection Angular Errors', fontsize=24) - plt.xlabel('Error (degrees)', fontsize=20) - plt.ylabel('Frequency', fontsize=20) - plt.legend() - plt.grid(True) - - plt.suptitle('Reprojection Error Histograms for UV and RGB Cameras', fontsize=28) - plt.tight_layout(rect=[0, 0.03, 1, 0.95]) - pdf.savefig() - plt.close() - - -def process_aligned_results(aligned_sparse_recon_subdir, colmap_dir, save_dir, - nav_state_provider, basename_to_time): - # --------------------------------------------------------------------------- - # Sanity check, pick the coordinates for a point in the 3-D model and - # convert them to latitude and longitude. - enu = np.array((640.446167, 822.111633, -9.576390)) - print(enu_to_llh(enu[0], enu[1], enu[2], nav_state_provider.lat0, - nav_state_provider.lon0, nav_state_provider.h0)) - - # Read in the Colmap details of all images. - images_bin_fname = osp.join(colmap_dir, - aligned_sparse_recon_subdir, - 'images.bin') - colmap_images = read_images_binary(images_bin_fname) - points_bin_fname = osp.join(colmap_dir, - aligned_sparse_recon_subdir, - 'points3D.bin') - points3d = read_points3D_binary(points_bin_fname) - camera_bin_fname = osp.join(colmap_dir, - aligned_sparse_recon_subdir, - 'cameras.bin') - colmap_cameras = read_cameras_binary(camera_bin_fname) - - """ - # For sanity checking that the original unadjusted results line up and the - # code itself is sound. - images_bin_fname = '%s/%s/images.bin' % (colmap_dir, sparse_recon_subdir) - colmap_images = read_images_binary(images_bin_fname) - points_bin_fname = '%s/%s/points3D.bin' % (colmap_dir, sparse_recon_subdir) - points3d = read_points3D_binary(points_bin_fname) - camera_bin_fname = '%s/%s/cameras.bin' % (colmap_dir, sparse_recon_subdir) - colmap_cameras = read_cameras_binary(camera_bin_fname) - """ - - if False: - pts_3d = [] - for pt_id in points3d: - pts_3d.append(points3d[pt_id].xyz) - - pts_3d = np.array(pts_3d).T - plt.plot(pts_3d[0], pts_3d[1], 'ro') - - - points_per_image, camera_from_camera_str = get_colmap_data(colmap_images, - colmap_cameras, - points3d, - basename_to_time) - - img_fnames, img_times, ins_poses, sfm_poses, llhs = process_images(colmap_images, - basename_to_time, - nav_state_provider) - - if False: - # Loop over all images and apply the camera model to project 3-D points - # into the image and compare to the measured versions to calculate - # reprojection error. - err = [] - for i in range(len(img_fnames)): - print('%i/%i' % (i + 1, len(img_fnames))) - fname = img_fnames[i] - sfm_pose = sfm_poses[i] - camera_str = osp.basename(osp.dirname(fname)) - - colmap_camera = camera_from_camera_str[camera_str] - - if colmap_camera.model == 'OPENCV': - fx, fy, cx, cy, d1, d2, d3, d4 = colmap_camera.params - elif colmap_camera.model == 'PINHOLE': - fx, fy, cx, cy = colmap_camera.params - d1 = d2 = d3 = d4 = 0 - - K = K = np.array([[fx, 0, cx], [0, fy, cy], [0, 0, 1]]) - dist = np.array([d1, d2, d3, d4]) - - cm = StandardCamera(colmap_camera.width, colmap_camera.height, K, dist, - [0, 0, 0], [0, 0, 0, 1], - platform_pose_provider=NavStateFixed(*sfm_pose)) - xy, xyz, t = points_per_image[fname] - err_ = np.sqrt(np.sum((xy - cm.project(xyz.T, t).T)**2, axis=1)) - err = err + err_.tolist() - - print("Errors: ") - print(np.mean(err)) - print(np.median(err)) - #plt.hist(err, 1000) - - - camera_strs = set([osp.basename(osp.dirname(fname)) for fname in img_fnames]) - rgb_camera_strs = set([ cam for cam in camera_strs if 'rgb' in cam ]) - uv_camera_strs = set([ cam for cam in camera_strs if 'uv' in cam ]) - - print("Calibrating RGB cameras.") - calibrate_rgb(rgb_camera_strs, img_fnames, ins_poses, sfm_poses, - points_per_image, camera_from_camera_str, - nav_state_provider, save_dir) - - time_to_modality = create_time_modality_mapping(colmap_images, basename_to_time) - fname_to_time_channel_modality = create_fname_to_time_channel_modality( - img_fnames, basename_to_time) - - print("Calibrating UV cameras.") - calibrate_uv(uv_camera_strs, img_fnames, colmap_images, - camera_from_camera_str, save_dir, - basename_to_time, time_to_modality, - fname_to_time_channel_modality, - colmap_dir, points_per_image) - print("Finished calibration!") - - -def main(): - # ---------------------------- Define Paths ---------------------------------- - # KAMERA flight directory where each sub-directory contains meta.json files. - flight_dir = '/home/local/KHQ/adam.romlein/noaa/data/2024_AOC_AK_Calibration/fl09' - - # You should have a colmap directory where all of the Colmap-generated files - # reside. - colmap_dir = '/home/local/KHQ/adam.romlein/noaa/data/2024_AOC_AK_Calibration/colmap' - - # Sub-directory containing the images.bin and cameras.bin. Set to '' if in the - # top-level Colmap directory. - sparse_recon_subdir = 'sparse/1' - aligned_sparse_recon_subdir = 'aligned/1' - - # Location to save KAMERA camera models. - save_dir = osp.join(flight_dir, 'kamera_models') - # ---------------------------------------------------------------------------- - - basename_to_time = get_basename_to_time(flight_dir) - - json_glob = pathlib.Path(flight_dir).rglob('*_meta.json') - try: - next(json_glob) - except StopIteration: - raise SystemExit("No meta jsons were found, please check your filepaths.") - nav_state_provider = NavStateINSJson(json_glob) - - # We take the INS-reported position (converted from latitude, longitude, and - # altitude into easting/northing/up coordinates) and assign it to each image. - print('Latiude of ENU coordinate system:', nav_state_provider.lat0, 'degrees') - print('Longitude of ENU coordinate system:', nav_state_provider.lon0, - 'degrees') - print('Height above the WGS84 ellipsoid of the ENU coordinate system:', - nav_state_provider.h0, 'meters') - - # ---------------------------------------------------------------------------- - # Assemble the list of filenames with paths relative to the 'images0' directory - # that we point Colmap to as the raw image directory. This may be a directory - # of images, or it might be a directory of subdirectories, each of which - # contains images from one camera. - - # Colmap then uses this pairing to solve for a similarity transform to best- - # match the SfM poses it recovered into these positions. All Colmap coordinates - # in this aligned version of its reconstruction will then be in easting/ - # northing/up meters coordinates - align_fname = os.path.join(colmap_dir, 'image_locations.txt') - print(align_fname) - if osp.exists(align_fname) and osp.exists(osp.join(colmap_dir, - aligned_sparse_recon_subdir)): - print(f"{align_fname} and {aligned_sparse_recon_subdir} exists," - " assuming model is aligned.") - else: - # Read in the Colmap details of all images. - images_bin_fname = osp.join(colmap_dir, sparse_recon_subdir, 'images.bin') - colmap_images = read_images_binary(images_bin_fname) - - img_fnames, img_times, ins_poses, sfm_poses, llhs = process_images(colmap_images, - basename_to_time, - nav_state_provider) - write_image_locations(align_fname, img_fnames, ins_poses) - ub.ensuredir(osp.join(colmap_dir, aligned_sparse_recon_subdir)) - print('Now run\nkamera/src/kitware-ros-pkg/postflight_scripts/scripts/' - 'colmap/model_aligner.sh %s %s %s %s' % (colmap_dir.replace('/host_filesystem', ''), - sparse_recon_subdir, - 'image_locations.txt', - aligned_sparse_recon_subdir)) - return - - process_aligned_results(aligned_sparse_recon_subdir, colmap_dir, - save_dir, nav_state_provider, - basename_to_time) - -if __name__ == "__main__": - main() diff --git a/kamera/postflight/scripts/create_flight_summary.py b/kamera/postflight/scripts/create_flight_summary.py index 7fca5b81..4634635b 100644 --- a/kamera/postflight/scripts/create_flight_summary.py +++ b/kamera/postflight/scripts/create_flight_summary.py @@ -1,8 +1,6 @@ #!/usr/bin/env python from __future__ import division, print_function import argparse -import os -import pathlib # Custom package imports. from kamera.postflight import utilities @@ -10,7 +8,7 @@ def main(): parser = argparse.ArgumentParser( - description="Convert all images from a " "flight into shapefiles." + description="Convert all images from a flight into shapefiles." ) parser.add_argument( "-flight_dir", @@ -22,7 +20,7 @@ def main(): ) parser.add_argument( "-output_dir", - help="Output directory (defaults to 'processed_results'.).", + help="Output directory (defaults to /processed_results).", type=str, default=None, ) @@ -36,15 +34,13 @@ def main(): # flight_dir = '/example_flight_dir' # output_dir = '/example_output_dir' - if not output_dir: - base_dir = pathlib.Path(flight_dir).parents[0] - output_dir = os.path.join(base_dir, "processed_results") - if not flight_dir: - raise SystemError("No flight dir specified! Please pass one as an argument or hardcode one in the file.") + raise SystemError( + "No flight dir specified! Please pass one as an argument or hardcode one in the file." + ) - utilities.create_flight_summary(flight_dir, output_dir) + utilities.create_flight_summary(flight_dir, output_dir=output_dir) -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/kamera/postflight/scripts/intercam_homography_from_yaml.py b/kamera/postflight/scripts/intercam_homography_from_yaml.py deleted file mode 100644 index 928248b5..00000000 --- a/kamera/postflight/scripts/intercam_homography_from_yaml.py +++ /dev/null @@ -1,138 +0,0 @@ -#!/usr/bin/env python -""" -Library handling projection operations of a standard camera model. -""" -from __future__ import division, print_function -import cv2 -import time -import os -import copy -import glob -import random -import json -import PIL -import numpy as np -import matplotlib.pyplot as plt -from numpy import pi -from mpl_toolkits.mplot3d import Axes3D -from scipy.optimize import minimize - -# Custom package imports. -from sensor_models import ( - quaternion_multiply, - quaternion_from_matrix, - quaternion_from_euler, - quaternion_slerp, - quaternion_inverse, - quaternion_matrix - ) -from colmap_processing.camera_models import load_from_file, StandardCamera -from colmap_processing.image_renderer import render_view - - -def process(camera_model_fname1, camera_model_fname2, out_fname): - src_cm = load_from_file(camera_model_fname1) - dst_cm = load_from_file(camera_model_fname2) - - x = np.linspace(0, dst_cm.width-1, dst_cm.width//2) - y = np.linspace(0, dst_cm.height-1, dst_cm.height//2) - X,Y = np.meshgrid(x, y) - im_pts = np.vstack([X.ravel(),Y.ravel()]) - - # Unproject rays into camera coordinate system. - _, ray_dir = dst_cm.unproject(im_pts, 0) - points = ray_dir*1e6 - - im_pts_src = src_cm.project(points, 0).astype(np.float32) - - # Remove coordinates outside of src camera. - ind = np.logical_and(im_pts_src[0] >= 0, im_pts_src[0] <= src_cm.width) - ind = np.logical_and(ind, im_pts_src[1] >= 0) - ind = np.logical_and(ind, im_pts_src[1] <= src_cm.height) - - im_pts = im_pts[:, ind] - im_pts_src = im_pts_src[:, ind] - - h, status = cv2.findHomography(im_pts_src.T, im_pts.T) - - # Error in using homography to represent transformation. - im_pts2 = np.dot(h, np.vstack([im_pts_src, np.ones(im_pts_src.shape[1])])) - im_pts2 = im_pts2[:2]/im_pts2[2] - err_forward = np.sqrt(np.sum((im_pts2 - im_pts)**2, axis=0)) - - im_pts2 = np.dot(np.linalg.inv(h), - np.vstack([im_pts, np.ones(im_pts.shape[1])])) - im_pts2 = im_pts2[:2]/im_pts2[2] - err_reverse = np.sqrt(np.sum((im_pts2 - im_pts_src)**2, axis=0)) - - base_dir, base_fname = os.path.split(out_fname) - base_fname = os.path.splitext(base_fname)[0] - - try: - os.makedirs(base_dir) - except (IOError, OSError): - pass - - np.savetxt(out_fname, h) - - fig = plt.figure(num=None, figsize=(15.3, 10.7), dpi=80) - plt.rc('font', **{'size': 40}) - plt.rc('axes', linewidth=4) - plt.subplot(121) - plt.plot(np.linspace(0, 100, len(err_forward)), np.sort(err_forward), - linewidth=6) - plt.xlabel('Percentile', fontsize=50) - plt.ylabel('Error (pixels)', fontsize=50) - plt.title('Forward', fontsize=50) - ax = plt.subplot(122) - plt.plot(np.linspace(0, 100, len(err_reverse)), np.sort(err_reverse), - linewidth=6) - plt.xlabel('Percentile', fontsize=50) - plt.ylabel('Error (pixels)', fontsize=50) - plt.title('Reverse', fontsize=50) - ax.yaxis.tick_right() - ax.yaxis.set_label_position("right") - fig.subplots_adjust(bottom=0.13) - fig.subplots_adjust(top=0.93) - fig.subplots_adjust(right=0.85) - fig.subplots_adjust(left=0.12) - plt.savefig('%s/%s_homog_approx_error.png' % (base_dir, base_fname)) - - -# Process all cameras. -base_dir = '/host_filesystem/mnt/homenas2/kamera/Calibration/fl08/kamera_models' -for dirname in os.listdir(base_dir): - if not os.path.isdir('%s/%s' % (base_dir, dirname)): - continue - - fnames = glob.glob('%s/%s/*_rgb.yaml' % (base_dir, dirname)) - if len(fnames) != 1: - continue - - rgb_camera_model_fname = fnames[0] - - fnames = glob.glob('%s/%s/*_uv.yaml' % (base_dir, dirname)) - if len(fnames) != 1: - continue - - uv_camera_model_fname = fnames[0] - - out_fname = '%s/%s/%s_to_%s_homography.txt' % (base_dir, dirname, 'uv', 'rgb') - process(uv_camera_model_fname, rgb_camera_model_fname, out_fname) - - out_fname = '%s/%s/%s_to_%s_homography.txt' % (base_dir, dirname, 'rgb', 'uv') - process(rgb_camera_model_fname, uv_camera_model_fname, out_fname) - - fnames = glob.glob('%s/%s/*_ir.yaml' % (base_dir, dirname)) - if len(fnames) != 1: - continue - - ir_camera_model_fname = fnames[0] - - print('Processing', dirname) - - out_fname = '%s/%s/%s_to_%s_homography.txt' % (base_dir, dirname, 'ir', 'rgb') - process(ir_camera_model_fname, rgb_camera_model_fname, out_fname) - - out_fname = '%s/%s/%s_to_%s_homography.txt' % (base_dir, dirname, 'rgb', 'ir') - process(rgb_camera_model_fname, ir_camera_model_fname, out_fname) diff --git a/kamera/postflight/utilities.py b/kamera/postflight/utilities.py index 0d6fd05e..b17983ba 100644 --- a/kamera/postflight/utilities.py +++ b/kamera/postflight/utilities.py @@ -5,7 +5,6 @@ import json import time import glob -import warnings import threading from shutil import copyfile import exifread @@ -22,21 +21,12 @@ import pygeodesy from osgeo import osr, gdal import simplekml -from shapely.geometry import Polygon, mapping +from shapely.geometry import Polygon import shapefile # Custom package imports. -import sys sys.path.insert(0, "C:/Users/path_to/postflight_scripts/sensor_models/src") -from kamera.sensor_models import ( - quaternion_multiply, - quaternion_from_matrix, - quaternion_from_euler, - quaternion_slerp, - quaternion_inverse, - quaternion_matrix, -) from kamera.colmap_processing.camera_models import load_from_file from kamera.sensor_models.nav_conversions import enu_to_llh, llh_to_enu from kamera.sensor_models.nav_state import NavStateINSJson @@ -415,17 +405,17 @@ def decompose_affine(A): def get_image_chip(image, left, right, top, bottom): - l = np.maximum(left, 0) - r = np.maximum(l, right) - r = np.minimum(r, image.shape[1]) - t = np.maximum(top, 0) - b = np.maximum(t, bottom) - b = np.minimum(b, image.shape[0]) + x0 = np.maximum(left, 0) + x1 = np.maximum(x0, right) + x1 = np.minimum(x1, image.shape[1]) + y0 = np.maximum(top, 0) + y1 = np.maximum(y0, bottom) + y1 = np.minimum(y1, image.shape[0]) if image.ndim == 3: - return image[t:b, l:r, :] + return image[y0:y1, x0:x1, :] else: - return image[t:b, l:r] + return image[y0:y1, x0:x1] def points_along_image_border(width, height, num_points=4): @@ -523,7 +513,7 @@ def parse_image_directory(image_dir, modality=None): with open(json_fname) as json_file: try: d = json.load(json_file) - except json.decoder.JSONDecodeError as e: + except json.decoder.JSONDecodeError: print("Failed to decode file %s." % json_file) continue @@ -772,7 +762,7 @@ def affine_not_valid(h): try: translation, R, scale, S = decompose_affine(h) - except: + except Exception: return True # translation = h[:2, 2] @@ -878,10 +868,6 @@ def affine_not_valid(h): mask = mask.ravel().astype(bool) - # Verify whether homography is acceptable. If not, do RANSAC with - # only acceptable test cases. - det = np.linalg.det(h) - pts0 = pts0[mask] pts1 = pts1[mask] @@ -1323,11 +1309,8 @@ def create_geotiffs_glob( # This will do some duplication of NavState parsing but I do not have time to fix ret = parse_image_directory(image_dir, modality=modality) - img_fname_to_time = ret[0] img_time_to_fname = ret[1] platform_pose_provider = ret[2] - effort_type = ret[3] - trigger_type = ret[4] camera_model = load_from_file(camera_model_fname, platform_pose_provider) @@ -1515,7 +1498,7 @@ def get_review_fate( t = basename_to_time[base_name] try: nth = nav_state_provider.time_to_save_every_x_image[t] - except KeyError as e: + except KeyError: print(f"Could not find 'save_every_x_image' for time {t}.") nth = None @@ -1577,7 +1560,7 @@ def get_basename_to_time(flight_dir) -> dict: return basename_to_time -def create_flight_summary(flight_dir, save_shapefile_per_image=False): +def create_flight_summary(flight_dir, save_shapefile_per_image=False, output_dir=None): """Create flight summary for a flight directory. A flight directory contains a folder structure where different @@ -1592,7 +1575,12 @@ def create_flight_summary(flight_dir, save_shapefile_per_image=False): //right_view //center_view + Results are written under ``output_dir``, by default + /processed_results. + """ + if output_dir is None: + output_dir = "%s/processed_results" % flight_dir top_tic = time.time() flight_id = os.path.basename(flight_dir) project_id = os.path.basename(os.path.dirname(flight_dir)) @@ -1623,7 +1611,7 @@ def create_flight_summary(flight_dir, save_shapefile_per_image=False): for f in det_txts: print(f) with open(f, "r") as of: - sets_detector_processed += [get_base(l) for l in of.readlines()] + sets_detector_processed += [get_base(line) for line in of.readlines()] sets_detector_processed = set(sets_detector_processed) print("Number of sets of images detected on: %s" % len(sets_detector_processed)) @@ -1634,8 +1622,8 @@ def create_flight_summary(flight_dir, save_shapefile_per_image=False): for f in det_csvs: with open(f, "r") as of: lines = of.readlines() - lines = [l for l in lines if l[0] != "#"] - files = [get_base(l.split(",")[1]) for l in lines] + lines = [line for line in lines if line[0] != "#"] + files = [get_base(line.split(",")[1]) for line in lines] sets_with_detections += files sets_with_detections = set(sets_with_detections) print("Number of sets with detections: %s" % len(sets_with_detections)) @@ -1647,10 +1635,7 @@ def create_flight_summary(flight_dir, save_shapefile_per_image=False): raise SystemExit("No meta jsons were found, please check your filepaths.") nav_state_provider = NavStateINSJson(json_glob) - fn_glob = os.path.join(flight_dir, "*/*/*meta.json") count = 0 - est_metas = glob.glob(fn_glob) - total = len(est_metas) * 3 for sys_config in os.listdir(flight_dir): sys_config_dir = "%s/%s" % (flight_dir, sys_config) @@ -1784,7 +1769,7 @@ def create_flight_summary(flight_dir, save_shapefile_per_image=False): # ------------------------------------------------------------------------ # Save homographies estimated by INS. - homog_dir = "%s/processed_results/homographies_img_to_lonlat" % (flight_dir) + homog_dir = "%s/homographies_img_to_lonlat" % (output_dir) for sys_str in fnames_by_system: homog_dir2 = "%s/%s" % (homog_dir, sys_str) @@ -1887,7 +1872,7 @@ def create_flight_summary(flight_dir, save_shapefile_per_image=False): shape_img_basenames.append(os.path.split(img_fname)[1]) shp_shapes_fnames.append(img_fname) if len(shp_shapes) > 0: - shapefile_dir = "%s/processed_results/fov_shapefiles/" % flight_dir + shapefile_dir = "%s/fov_shapefiles/" % output_dir try: os.makedirs(shapefile_dir) @@ -1950,8 +1935,8 @@ def create_flight_summary(flight_dir, save_shapefile_per_image=False): if save_shapefile_per_image: # Write each individual frame as a seperate shapefile. - shapefile_dir = "%s/processed_results/fov_shapefiles/%s_fovs" % ( - flight_dir, + shapefile_dir = "%s/fov_shapefiles/%s_fovs" % ( + output_dir, sys_str, ) @@ -2020,7 +2005,7 @@ def create_flight_summary(flight_dir, save_shapefile_per_image=False): # Convert INS tracks to CSVs # ------------------------------------------------------------------------ - dir_out = "%s/processed_results/ins_csvs" % flight_dir + dir_out = "%s/ins_csvs" % output_dir try: os.makedirs(dir_out) except OSError: @@ -2063,12 +2048,10 @@ def visualize_registration_homographies(flight_dir, sys_str="rgb"): """ img_to_lonlat_homog_dir = ( - "%s/processed_results/" "homographies_img_to_lonlat" % flight_dir + "%s/processed_results/homographies_img_to_lonlat" % flight_dir ) - img_to_img_homog_dir = ( - "%s/processed_results/" "homographies_img_to_img" % flight_dir - ) + img_to_img_homog_dir = "%s/processed_results/homographies_img_to_img" % flight_dir dir_out = "%s/processed_results/ins_registration_viz" % flight_dir @@ -2166,7 +2149,6 @@ def get_image(fname): img_pair_fnames = sorted(list(img_to_img_homog.keys())) for img_pair_fname in img_pair_fnames: - fname1, fname2 = img_pair_fname.split("_to_") h12 = img_to_img_homog[img_pair_fname] img1 = get_image(fname1) @@ -2274,12 +2256,12 @@ def detection_summary( if img_to_lonlat_homog_dir is None: img_to_lonlat_homog_dir = ( - "%s/processed_results/" "homographies_img_to_lonlat" % flight_dir + "%s/processed_results/homographies_img_to_lonlat" % flight_dir ) if img_to_img_homog_dir is None: img_to_img_homog_dir = ( - "%s/processed_results/" "homographies_img_to_img" % flight_dir + "%s/processed_results/homographies_img_to_img" % flight_dir ) if not os.path.isdir(img_to_lonlat_homog_dir): @@ -2471,7 +2453,7 @@ def get_image(fname): return img # Track redundant detections. - print2("Comparing detections between frames to identify redundant " "detections...") + print2("Comparing detections between frames to identify redundant detections...") num_suppressed = 0 img_fnames = sorted(img_fnames) diff --git a/kamera/sensor_models/nav_conversions.py b/kamera/sensor_models/nav_conversions.py index 6246e3b1..ec349b4f 100644 --- a/kamera/sensor_models/nav_conversions.py +++ b/kamera/sensor_models/nav_conversions.py @@ -37,22 +37,22 @@ - sudo apt-get install geographiclib-tools """ + from __future__ import division, print_function import numpy as np import subprocess from math import cos, sin, sqrt from kamera.sensor_models import ( - quaternion_multiply, - quaternion_inverse, - quaternion_slerp - ) + quaternion_multiply, + quaternion_inverse, +) # WGS84 constants _a = 6378137 -_f = 1/(298257223563/1000000000) -_e2 = _f*(2-_f) -_e2m = np.square(1-_f) +_f = 1 / (298257223563 / 1000000000) +_e2 = _f * (2 - _f) +_e2m = np.square(1 - _f) _e2a = abs(_e2) _e4a = np.square(_e2) epsilon = np.finfo(float).eps @@ -108,32 +108,40 @@ def llh_to_enu(lat, lon, h, lat0, lon0, h0, in_degrees=True, pure_python=True): """ if not in_degrees: - lat = lat*180/np.pi - lon = lon*180/np.pi - lat0 = lat0*180/np.pi - lon0 = lon0*180/np.pi + lat = lat * 180 / np.pi + lon = lon * 180 / np.pi + lat0 = lat0 * 180 / np.pi + lon0 = lon0 * 180 / np.pi if pure_python: sphi, cphi = sincosd(lat0) slam, clam = sincosd(lon0) _r = geocentric_rotation(sphi, cphi, slam, clam) - xc,yc,zc = llh_to_ecef(lat, lon, h, in_degrees=True) - _x0,_y0,_z0 = llh_to_ecef(lat0, lon0, h0, in_degrees=True) - xc -= _x0; yc -= _y0; zc -= _z0; - x = _r[0] * xc + _r[3] * yc + _r[6] * zc; - y = _r[1] * xc + _r[4] * yc + _r[7] * zc; - z = _r[2] * xc + _r[5] * yc + _r[8] * zc; - return [x,y,z] + xc, yc, zc = llh_to_ecef(lat, lon, h, in_degrees=True) + _x0, _y0, _z0 = llh_to_ecef(lat0, lon0, h0, in_degrees=True) + xc -= _x0 + yc -= _y0 + zc -= _z0 + x = _r[0] * xc + _r[3] * yc + _r[6] * zc + y = _r[1] * xc + _r[4] * yc + _r[7] * zc + z = _r[2] * xc + _r[5] * yc + _r[8] * zc + return [x, y, z] else: - output = subprocess.check_output(['CartConvert','-l', - str(lat0),str(lon0), - str(h0),'--input-string', - ' '.join([str(lat),str(lon),str(h)])]) - return [float(s) for s in output.split('\n')[0].split(' ')] + output = subprocess.check_output( + [ + "CartConvert", + "-l", + str(lat0), + str(lon0), + str(h0), + "--input-string", + " ".join([str(lat), str(lon), str(h)]), + ] + ) + return [float(s) for s in output.split("\n")[0].split(" ")] -def enu_to_llh(east, north, up, lat0, lon0, h0, in_degrees=True, - pure_python=True): +def enu_to_llh(east, north, up, lat0, lon0, h0, in_degrees=True, pure_python=True): """Convert latitude, longitude, and height to east, north, up. East, north, and up are coordinates within a local level Cartesian @@ -183,33 +191,41 @@ def enu_to_llh(east, north, up, lat0, lon0, h0, in_degrees=True, """ if not in_degrees: - lat0 = lat0*180/np.pi - lon0 = lon0*180/np.pi + lat0 = lat0 * 180 / np.pi + lon0 = lon0 * 180 / np.pi if pure_python: x, y, z = east, north, up sphi, cphi = sincosd(lat0) slam, clam = sincosd(lon0) _r = geocentric_rotation(sphi, cphi, slam, clam) - _x0,_y0,_z0 = llh_to_ecef(lat0, lon0, h0, in_degrees=True) + _x0, _y0, _z0 = llh_to_ecef(lat0, lon0, h0, in_degrees=True) - xc = _x0 + _r[0] * x + _r[1] * y + _r[2] * z, - yc = _y0 + _r[3] * x + _r[4] * y + _r[5] * z, - zc = _z0 + _r[6] * x + _r[7] * y + _r[8] * z; + xc = _x0 + _r[0] * x + _r[1] * y + _r[2] * z + yc = _y0 + _r[3] * x + _r[4] * y + _r[5] * z + zc = _z0 + _r[6] * x + _r[7] * y + _r[8] * z lat, lon, h = ecef_to_llh(xc, yc, zc, in_degrees) else: - output = subprocess.check_output(['CartConvert','-r','-l',str(lat0), - str(lon0),str(h0),'--input-string', - ' '.join([str(east),str(north), - str(up)])]) + output = subprocess.check_output( + [ + "CartConvert", + "-r", + "-l", + str(lat0), + str(lon0), + str(h0), + "--input-string", + " ".join([str(east), str(north), str(up)]), + ] + ) - lat, lon, h = [float(s) for s in output.split('\n')[0].split(' ')] + lat, lon, h = [float(s) for s in output.split("\n")[0].split(" ")] if not in_degrees: - lat = lat*180/np.pi - lon = lon*180/np.pi + lat = lat * 180 / np.pi + lon = lon * 180 / np.pi - return [lat,lon,h] + return [lat, lon, h] def ned_quat_to_enu_quat(quat): @@ -223,7 +239,7 @@ def ned_quat_to_enu_quat(quat): :rtype: 4-array """ - return quaternion_multiply([np.sqrt(2)/2,np.sqrt(2)/2,0,0], quat) + return quaternion_multiply([np.sqrt(2) / 2, np.sqrt(2) / 2, 0, 0], quat) def enu_quat_to_ned_quat(quat): @@ -237,7 +253,7 @@ def enu_quat_to_ned_quat(quat): :rtype: 4-array """ - return quaternion_multiply([np.sqrt(2)/2,np.sqrt(2)/2,0,0], quat) + return quaternion_multiply([np.sqrt(2) / 2, np.sqrt(2) / 2, 0, 0], quat) def ecef_to_llh(X, Y, Z, in_degrees=True): @@ -266,7 +282,7 @@ def ecef_to_llh(X, Y, Z, in_degrees=True): z = 2167698 """ - R = np.hypot(X,Y) + R = np.hypot(X, Y) if R == 0: slam = 0 clam = 1 @@ -274,25 +290,25 @@ def ecef_to_llh(X, Y, Z, in_degrees=True): slam = Y / R clam = X / R - h = np.hypot(R,Z) # Distance to center of earth - if (h > _maxrad): + h = np.hypot(R, Z) # Distance to center of earth + if h > _maxrad: # We really far away (> 12 million light years) treat the earth as a # point and h, above, is an acceptable approximation to the height. # This avoids overflow, e.g., in the computation of disc below. It's # possible that h has overflowed to inf but that's OK. # # Treat the case X, Y finite, but R overflows to +inf by scaling by 2. - R = np.hypot(X/2, Y/2) + R = np.hypot(X / 2, Y / 2) if R == 0: slam = 0 clam = 1 else: - slam = (Y/2) / R - clam = (X/2) / R + slam = (Y / 2) / R + clam = (X / 2) / R - H = np.hypot(Z/2,R) - sphi = (Z/2) / H + H = np.hypot(Z / 2, R) + sphi = (Z / 2) / H cphi = R / H elif _e4a == 0: # Treat the spherical case. Dealing with underflow in the general case @@ -314,17 +330,17 @@ def ecef_to_llh(X, Y, Z, in_degrees=True): q = _e2m * np.square(Z / _a) r = (p + q - _e4a) / 6 if _f < 0: - p,q = q,p + p, q = q, p if not (_e4a * q == 0 and r <= 0): # Avoid possible division by zero when r = 0 by multiplying # equations for s and t by r^3 and r, resp. - S = _e4a * p * q / 4 # S = r^3 * s + S = _e4a * p * q / 4 # S = r^3 * s r2 = np.square(r) r3 = r * r2 disc = S * (2 * r3 + S) u = r - if (disc >= 0): + if disc >= 0: T3 = S + r3 # Pick the sign on the sqrt to maximize abs(T3). This # minimizes loss of precision due to cancellation. The result @@ -336,7 +352,7 @@ def ecef_to_llh(X, Y, Z, in_degrees=True): T3 += np.sqrt(disc) # N.B. cbrt always returns the real root. cbrt(-8) = -2. - T = np.cbrt(T3) # T = r * t + T = np.cbrt(T3) # T = r * t # T can be zero but then r2 / T -> 0. if T != 0: u += T + (r2 / T) @@ -349,7 +365,7 @@ def ecef_to_llh(X, Y, Z, in_degrees=True): # r < 0. u += 2 * r * np.cos(ang / 3) - v = np.sqrt(np.square(u) + _e4a * q) # guaranteed positive + v = np.sqrt(np.square(u) + _e4a * q) # guaranteed positive # Avoid loss of accuracy when u < 0. Underflow doesn't occur in # e4 * q / (v - u) because u ~ e^4 when q is small and u < 0. if u < 0: # u+v guaranteed positive @@ -370,12 +386,12 @@ def ecef_to_llh(X, Y, Z, in_degrees=True): k2 = k d = k1 * R / k2 - H = np.hypot(Z/k1, R/k2) - sphi = (Z/k1) / H - cphi = (R/k2) / H - h = (1 - _e2m/k1) * np.hypot(d, Z) + H = np.hypot(Z / k1, R / k2) + sphi = (Z / k1) / H + cphi = (R / k2) / H + h = (1 - _e2m / k1) * np.hypot(d, Z) - else: # e4 * q == 0 && r <= 0 + else: # e4 * q == 0 && r <= 0 # This leads to k = 0 (oblate, equatorial plane) and k + e^2 = 0 # (prolate, rotation axis) and the generation of 0/0 in the general # formulas for phi and h. using the general formula and division by 0 @@ -387,7 +403,7 @@ def ecef_to_llh(X, Y, Z, in_degrees=True): else: zz = np.sqrt(p / _e2m) - if _f < 0: + if _f < 0: xx = np.sqrt(_e4a - p) else: xx = np.sqrt(p) @@ -396,15 +412,15 @@ def ecef_to_llh(X, Y, Z, in_degrees=True): sphi = zz / H cphi = xx / H if Z < 0: - sphi = -sphi # for tiny negative Z (not for prolate) + sphi = -sphi # for tiny negative Z (not for prolate) if _f >= 0: - h = - _a * (_e2m) * H / _e2a + h = -_a * (_e2m) * H / _e2a else: - h = - _a * (1) * H / _e2a + h = -_a * (1) * H / _e2a - lat = float(np.arctan2(sphi, cphi)*180/np.pi) - lon = float(np.arctan2(slam, clam)*180/np.pi) + lat = float(np.arctan2(sphi, cphi) * 180 / np.pi) + lon = float(np.arctan2(slam, clam) * 180 / np.pi) return lat, lon, h @@ -429,18 +445,18 @@ def llh_to_ecef(lat, lon, h, in_degrees=True): """ if not in_degrees: - lat = lat*180/np.pi - lon = lon*180/np.pi + lat = lat * 180 / np.pi + lon = lon * 180 / np.pi - sphi,cphi = sincosd(lat) - slam,clam = sincosd(lon) + sphi, cphi = sincosd(lat) + slam, clam = sincosd(lon) - n = _a/np.sqrt(1-_e2*np.square(sphi)) + n = _a / np.sqrt(1 - _e2 * np.square(sphi)) Z = (_e2m * n + h) * sphi X = (n + h) * cphi Y = X * slam X *= clam - return [float(X),float(Y),float(Z)] + return [float(X), float(Y), float(Z)] def geocentric_rotation(sphi, cphi, slam, clam): @@ -455,16 +471,22 @@ def geocentric_rotation(sphi, cphi, slam, clam): """ M = np.zeros(9) # Local X axis (east) in geocentric coords - M[0] = -slam; M[3] = clam; M[6] = 0; + M[0] = -slam + M[3] = clam + M[6] = 0 # Local Y axis (north) in geocentric coords - M[1] = -clam * sphi; M[4] = -slam * sphi; M[7] = cphi; + M[1] = -clam * sphi + M[4] = -slam * sphi + M[7] = cphi # Local Z axis (up) in geocentric coords - M[2] = clam * cphi; M[5] = slam * cphi; M[8] = sphi; + M[2] = clam * cphi + M[5] = slam * cphi + M[8] = sphi return M def sincosd(x): - """ + r""" * Evaluate the sine and cosine function with the argument in degrees * * @tparam T the type of the arguments. @@ -491,13 +513,17 @@ def sincosd(x): s = x if np.uint8(q) & np.uint8(3) == np.uint(0): - sinx = s; cosx = c + sinx = s + cosx = c elif np.uint8(q) & np.uint8(3) == np.uint(1): - sinx = c; cosx = -s + sinx = c + cosx = -s elif np.uint8(q) & np.uint8(3) == np.uint(2): - sinx = -s; cosx = -c + sinx = -s + cosx = -c else: - sinx = -c; cosx = s + sinx = -c + cosx = s # Set sign of 0 results. -0 only produced for sin(-0) if x: @@ -522,17 +548,21 @@ def rmat_enu_ecef(lat, lon, in_degrees=True): """ if in_degrees: - lat = lat/180*np.pi - lon = lon/180*np.pi + lat = lat / 180 * np.pi + lon = lon / 180 * np.pi clat = cos(lat) slat = sin(lat) clon = cos(lon) slon = sin(lon) - return np.array([[-slon, -slat*clon, clat*clon], - [clon, -slat*slon, clat*slon], - [0, clat, slat]]) + return np.array( + [ + [-slon, -slat * clon, clat * clon], + [clon, -slat * slon, clat * slon], + [0, clat, slat], + ] + ) def quat_enu_ecef(lat, lon, in_degrees=True): @@ -570,20 +600,20 @@ def quat_ecef_enu(lat, lon, in_degrees=True): """ if in_degrees: - lat = lat/180*np.pi - lon = lon/180*np.pi + lat = lat / 180 * np.pi + lon = lon / 180 * np.pi # This operator needs to rotate the axes of the ECEF coordinate system into # the ENU coordinate system. # First rotate around 90 degrees around ECEF Z. - q1 = np.array([0, 0, 1/sqrt(2), 1/sqrt(2)]) + q1 = np.array([0, 0, 1 / sqrt(2), 1 / sqrt(2)]) # Rotate around ECEF Y by latitude. - q2 = np.array([0, sin((np.pi/2 - lat)/2), 0, cos((np.pi/2 - lat)/2)]) + q2 = np.array([0, sin((np.pi / 2 - lat) / 2), 0, cos((np.pi / 2 - lat) / 2)]) # Rotate around ECEF Z by longitude. - q3 = np.array([0, 0, sin(lon/2), cos(lon/2)]) + q3 = np.array([0, 0, sin(lon / 2), cos(lon / 2)]) q = quaternion_multiply(q3, quaternion_multiply(q2, q1)) @@ -605,14 +635,18 @@ def rmat_ecef_enu(lat, lon, in_degrees=True): """ if in_degrees: - lat = lat/180*np.pi - lon = lon/180*np.pi + lat = lat / 180 * np.pi + lon = lon / 180 * np.pi clat = cos(lat) slat = sin(lat) clon = cos(lon) slon = sin(lon) - return np.array([[-slon, clon, 0], - [-clon*slat, -slon*slat, clat], - [clon*clat, slon*clat, slat]]) + return np.array( + [ + [-slon, clon, 0], + [-clon * slat, -slon * slat, clat], + [clon * clat, slon * clat, slat], + ] + ) diff --git a/poetry.lock b/poetry.lock deleted file mode 100644 index 689f34d6..00000000 --- a/poetry.lock +++ /dev/null @@ -1,1421 +0,0 @@ -# This file is automatically @generated by Poetry and should not be changed by hand. - -[[package]] -name = "asttokens" -version = "2.4.1" -description = "Annotate AST trees with source code positions" -category = "main" -optional = false -python-versions = "*" -files = [ - {file = "asttokens-2.4.1-py2.py3-none-any.whl", hash = "sha256:051ed49c3dcae8913ea7cd08e46a606dba30b79993209636c4875bc1d637bc24"}, - {file = "asttokens-2.4.1.tar.gz", hash = "sha256:b03869718ba9a6eb027e134bfdf69f38a236d681c83c160d510768af11254ba0"}, -] - -[package.dependencies] -six = ">=1.12.0" - -[package.extras] -astroid = ["astroid (>=1,<2)", "astroid (>=2,<4)"] -test = ["astroid (>=1,<2)", "astroid (>=2,<4)", "pytest"] - -[[package]] -name = "colorama" -version = "0.4.6" -description = "Cross-platform colored terminal text." -category = "main" -optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" -files = [ - {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, - {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, -] - -[[package]] -name = "contourpy" -version = "1.3.0" -description = "Python library for calculating contours of 2D quadrilateral grids" -category = "main" -optional = false -python-versions = ">=3.9" -files = [ - {file = "contourpy-1.3.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:880ea32e5c774634f9fcd46504bf9f080a41ad855f4fef54f5380f5133d343c7"}, - {file = "contourpy-1.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:76c905ef940a4474a6289c71d53122a4f77766eef23c03cd57016ce19d0f7b42"}, - {file = "contourpy-1.3.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:92f8557cbb07415a4d6fa191f20fd9d2d9eb9c0b61d1b2f52a8926e43c6e9af7"}, - {file = "contourpy-1.3.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:36f965570cff02b874773c49bfe85562b47030805d7d8360748f3eca570f4cab"}, - {file = "contourpy-1.3.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cacd81e2d4b6f89c9f8a5b69b86490152ff39afc58a95af002a398273e5ce589"}, - {file = "contourpy-1.3.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:69375194457ad0fad3a839b9e29aa0b0ed53bb54db1bfb6c3ae43d111c31ce41"}, - {file = "contourpy-1.3.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:7a52040312b1a858b5e31ef28c2e865376a386c60c0e248370bbea2d3f3b760d"}, - {file = "contourpy-1.3.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:3faeb2998e4fcb256542e8a926d08da08977f7f5e62cf733f3c211c2a5586223"}, - {file = "contourpy-1.3.0-cp310-cp310-win32.whl", hash = "sha256:36e0cff201bcb17a0a8ecc7f454fe078437fa6bda730e695a92f2d9932bd507f"}, - {file = "contourpy-1.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:87ddffef1dbe5e669b5c2440b643d3fdd8622a348fe1983fad7a0f0ccb1cd67b"}, - {file = "contourpy-1.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0fa4c02abe6c446ba70d96ece336e621efa4aecae43eaa9b030ae5fb92b309ad"}, - {file = "contourpy-1.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:834e0cfe17ba12f79963861e0f908556b2cedd52e1f75e6578801febcc6a9f49"}, - {file = "contourpy-1.3.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dbc4c3217eee163fa3984fd1567632b48d6dfd29216da3ded3d7b844a8014a66"}, - {file = "contourpy-1.3.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4865cd1d419e0c7a7bf6de1777b185eebdc51470800a9f42b9e9decf17762081"}, - {file = "contourpy-1.3.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:303c252947ab4b14c08afeb52375b26781ccd6a5ccd81abcdfc1fafd14cf93c1"}, - {file = "contourpy-1.3.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:637f674226be46f6ba372fd29d9523dd977a291f66ab2a74fbeb5530bb3f445d"}, - {file = "contourpy-1.3.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:76a896b2f195b57db25d6b44e7e03f221d32fe318d03ede41f8b4d9ba1bff53c"}, - {file = "contourpy-1.3.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:e1fd23e9d01591bab45546c089ae89d926917a66dceb3abcf01f6105d927e2cb"}, - {file = "contourpy-1.3.0-cp311-cp311-win32.whl", hash = "sha256:d402880b84df3bec6eab53cd0cf802cae6a2ef9537e70cf75e91618a3801c20c"}, - {file = "contourpy-1.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:6cb6cc968059db9c62cb35fbf70248f40994dfcd7aa10444bbf8b3faeb7c2d67"}, - {file = "contourpy-1.3.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:570ef7cf892f0afbe5b2ee410c507ce12e15a5fa91017a0009f79f7d93a1268f"}, - {file = "contourpy-1.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:da84c537cb8b97d153e9fb208c221c45605f73147bd4cadd23bdae915042aad6"}, - {file = "contourpy-1.3.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0be4d8425bfa755e0fd76ee1e019636ccc7c29f77a7c86b4328a9eb6a26d0639"}, - {file = "contourpy-1.3.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9c0da700bf58f6e0b65312d0a5e695179a71d0163957fa381bb3c1f72972537c"}, - {file = "contourpy-1.3.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:eb8b141bb00fa977d9122636b16aa67d37fd40a3d8b52dd837e536d64b9a4d06"}, - {file = "contourpy-1.3.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3634b5385c6716c258d0419c46d05c8aa7dc8cb70326c9a4fb66b69ad2b52e09"}, - {file = "contourpy-1.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:0dce35502151b6bd35027ac39ba6e5a44be13a68f55735c3612c568cac3805fd"}, - {file = "contourpy-1.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:aea348f053c645100612b333adc5983d87be69acdc6d77d3169c090d3b01dc35"}, - {file = "contourpy-1.3.0-cp312-cp312-win32.whl", hash = "sha256:90f73a5116ad1ba7174341ef3ea5c3150ddf20b024b98fb0c3b29034752c8aeb"}, - {file = "contourpy-1.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:b11b39aea6be6764f84360fce6c82211a9db32a7c7de8fa6dd5397cf1d079c3b"}, - {file = "contourpy-1.3.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:3e1c7fa44aaae40a2247e2e8e0627f4bea3dd257014764aa644f319a5f8600e3"}, - {file = "contourpy-1.3.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:364174c2a76057feef647c802652f00953b575723062560498dc7930fc9b1cb7"}, - {file = "contourpy-1.3.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:32b238b3b3b649e09ce9aaf51f0c261d38644bdfa35cbaf7b263457850957a84"}, - {file = "contourpy-1.3.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d51fca85f9f7ad0b65b4b9fe800406d0d77017d7270d31ec3fb1cc07358fdea0"}, - {file = "contourpy-1.3.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:732896af21716b29ab3e988d4ce14bc5133733b85956316fb0c56355f398099b"}, - {file = "contourpy-1.3.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d73f659398a0904e125280836ae6f88ba9b178b2fed6884f3b1f95b989d2c8da"}, - {file = "contourpy-1.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:c6c7c2408b7048082932cf4e641fa3b8ca848259212f51c8c59c45aa7ac18f14"}, - {file = "contourpy-1.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f317576606de89da6b7e0861cf6061f6146ead3528acabff9236458a6ba467f8"}, - {file = "contourpy-1.3.0-cp313-cp313-win32.whl", hash = "sha256:31cd3a85dbdf1fc002280c65caa7e2b5f65e4a973fcdf70dd2fdcb9868069294"}, - {file = "contourpy-1.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:4553c421929ec95fb07b3aaca0fae668b2eb5a5203d1217ca7c34c063c53d087"}, - {file = "contourpy-1.3.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:345af746d7766821d05d72cb8f3845dfd08dd137101a2cb9b24de277d716def8"}, - {file = "contourpy-1.3.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:3bb3808858a9dc68f6f03d319acd5f1b8a337e6cdda197f02f4b8ff67ad2057b"}, - {file = "contourpy-1.3.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:420d39daa61aab1221567b42eecb01112908b2cab7f1b4106a52caaec8d36973"}, - {file = "contourpy-1.3.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4d63ee447261e963af02642ffcb864e5a2ee4cbfd78080657a9880b8b1868e18"}, - {file = "contourpy-1.3.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:167d6c890815e1dac9536dca00828b445d5d0df4d6a8c6adb4a7ec3166812fa8"}, - {file = "contourpy-1.3.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:710a26b3dc80c0e4febf04555de66f5fd17e9cf7170a7b08000601a10570bda6"}, - {file = "contourpy-1.3.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:75ee7cb1a14c617f34a51d11fa7524173e56551646828353c4af859c56b766e2"}, - {file = "contourpy-1.3.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:33c92cdae89ec5135d036e7218e69b0bb2851206077251f04a6c4e0e21f03927"}, - {file = "contourpy-1.3.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a11077e395f67ffc2c44ec2418cfebed032cd6da3022a94fc227b6faf8e2acb8"}, - {file = "contourpy-1.3.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e8134301d7e204c88ed7ab50028ba06c683000040ede1d617298611f9dc6240c"}, - {file = "contourpy-1.3.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e12968fdfd5bb45ffdf6192a590bd8ddd3ba9e58360b29683c6bb71a7b41edca"}, - {file = "contourpy-1.3.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fd2a0fc506eccaaa7595b7e1418951f213cf8255be2600f1ea1b61e46a60c55f"}, - {file = "contourpy-1.3.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4cfb5c62ce023dfc410d6059c936dcf96442ba40814aefbfa575425a3a7f19dc"}, - {file = "contourpy-1.3.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:68a32389b06b82c2fdd68276148d7b9275b5f5cf13e5417e4252f6d1a34f72a2"}, - {file = "contourpy-1.3.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:94e848a6b83da10898cbf1311a815f770acc9b6a3f2d646f330d57eb4e87592e"}, - {file = "contourpy-1.3.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:d78ab28a03c854a873787a0a42254a0ccb3cb133c672f645c9f9c8f3ae9d0800"}, - {file = "contourpy-1.3.0-cp39-cp39-win32.whl", hash = "sha256:81cb5ed4952aae6014bc9d0421dec7c5835c9c8c31cdf51910b708f548cf58e5"}, - {file = "contourpy-1.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:14e262f67bd7e6eb6880bc564dcda30b15e351a594657e55b7eec94b6ef72843"}, - {file = "contourpy-1.3.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:fe41b41505a5a33aeaed2a613dccaeaa74e0e3ead6dd6fd3a118fb471644fd6c"}, - {file = "contourpy-1.3.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eca7e17a65f72a5133bdbec9ecf22401c62bcf4821361ef7811faee695799779"}, - {file = "contourpy-1.3.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:1ec4dc6bf570f5b22ed0d7efba0dfa9c5b9e0431aeea7581aa217542d9e809a4"}, - {file = "contourpy-1.3.0-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:00ccd0dbaad6d804ab259820fa7cb0b8036bda0686ef844d24125d8287178ce0"}, - {file = "contourpy-1.3.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8ca947601224119117f7c19c9cdf6b3ab54c5726ef1d906aa4a69dfb6dd58102"}, - {file = "contourpy-1.3.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:c6ec93afeb848a0845a18989da3beca3eec2c0f852322efe21af1931147d12cb"}, - {file = "contourpy-1.3.0.tar.gz", hash = "sha256:7ffa0db17717a8ffb127efd0c95a4362d996b892c2904db72428d5b52e1938a4"}, -] - -[package.dependencies] -numpy = ">=1.23" - -[package.extras] -bokeh = ["bokeh", "selenium"] -docs = ["furo", "sphinx (>=7.2)", "sphinx-copybutton"] -mypy = ["contourpy[bokeh,docs]", "docutils-stubs", "mypy (==1.11.1)", "types-Pillow"] -test = ["Pillow", "contourpy[test-no-images]", "matplotlib"] -test-no-images = ["pytest", "pytest-cov", "pytest-rerunfailures", "pytest-xdist", "wurlitzer"] - -[[package]] -name = "cycler" -version = "0.12.1" -description = "Composable style cycles" -category = "main" -optional = false -python-versions = ">=3.8" -files = [ - {file = "cycler-0.12.1-py3-none-any.whl", hash = "sha256:85cef7cff222d8644161529808465972e51340599459b8ac3ccbac5a854e0d30"}, - {file = "cycler-0.12.1.tar.gz", hash = "sha256:88bb128f02ba341da8ef447245a9e138fae777f6a23943da4540077d3601eb1c"}, -] - -[package.extras] -docs = ["ipython", "matplotlib", "numpydoc", "sphinx"] -tests = ["pytest", "pytest-cov", "pytest-xdist"] - -[[package]] -name = "datetime" -version = "5.5" -description = "This package provides a DateTime data type, as known from Zope. Unless you need to communicate with Zope APIs, you're probably better off using Python's built-in datetime module." -category = "main" -optional = false -python-versions = ">=3.7" -files = [ - {file = "DateTime-5.5-py3-none-any.whl", hash = "sha256:0abf6c51cb4ba7cee775ca46ccc727f3afdde463be28dbbe8803631fefd4a120"}, - {file = "DateTime-5.5.tar.gz", hash = "sha256:21ec6331f87a7fcb57bd7c59e8a68bfffe6fcbf5acdbbc7b356d6a9a020191d3"}, -] - -[package.dependencies] -pytz = "*" -"zope.interface" = "*" - -[[package]] -name = "decorator" -version = "5.1.1" -description = "Decorators for Humans" -category = "main" -optional = false -python-versions = ">=3.5" -files = [ - {file = "decorator-5.1.1-py3-none-any.whl", hash = "sha256:b8c3f85900b9dc423225913c5aace94729fe1fa9763b38939a95226f02d37186"}, - {file = "decorator-5.1.1.tar.gz", hash = "sha256:637996211036b6385ef91435e4fae22989472f9d571faba8927ba8253acbc330"}, -] - -[[package]] -name = "exceptiongroup" -version = "1.2.2" -description = "Backport of PEP 654 (exception groups)" -category = "main" -optional = false -python-versions = ">=3.7" -files = [ - {file = "exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b"}, - {file = "exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc"}, -] - -[package.extras] -test = ["pytest (>=6)"] - -[[package]] -name = "executing" -version = "2.1.0" -description = "Get the currently executing AST node of a frame, and other information" -category = "main" -optional = false -python-versions = ">=3.8" -files = [ - {file = "executing-2.1.0-py2.py3-none-any.whl", hash = "sha256:8d63781349375b5ebccc3142f4b30350c0cd9c79f921cde38be2be4637e98eaf"}, - {file = "executing-2.1.0.tar.gz", hash = "sha256:8ea27ddd260da8150fa5a708269c4a10e76161e2496ec3e587da9e3c0fe4b9ab"}, -] - -[package.extras] -tests = ["asttokens (>=2.1.0)", "coverage", "coverage-enable-subprocess", "ipython", "littleutils", "pytest", "rich"] - -[[package]] -name = "exifread" -version = "3.0.0" -description = "Read Exif metadata from tiff and jpeg files." -category = "main" -optional = false -python-versions = "*" -files = [ - {file = "ExifRead-3.0.0-py3-none-any.whl", hash = "sha256:2c5c59ef03b3bbee75b82b82d2498006b3c13509f35c9a76c7552faff73fa2d5"}, - {file = "ExifRead-3.0.0.tar.gz", hash = "sha256:0ac5a364169dbdf2bd62f94f5c073970ab6694a3166177f5e448b10c943e2ca4"}, -] - -[package.extras] -dev = ["mypy (==0.950)", "pylint (==2.13.8)"] - -[[package]] -name = "fonttools" -version = "4.54.1" -description = "Tools to manipulate font files" -category = "main" -optional = false -python-versions = ">=3.8" -files = [ - {file = "fonttools-4.54.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7ed7ee041ff7b34cc62f07545e55e1468808691dddfd315d51dd82a6b37ddef2"}, - {file = "fonttools-4.54.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:41bb0b250c8132b2fcac148e2e9198e62ff06f3cc472065dff839327945c5882"}, - {file = "fonttools-4.54.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7965af9b67dd546e52afcf2e38641b5be956d68c425bef2158e95af11d229f10"}, - {file = "fonttools-4.54.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:278913a168f90d53378c20c23b80f4e599dca62fbffae4cc620c8eed476b723e"}, - {file = "fonttools-4.54.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:0e88e3018ac809b9662615072dcd6b84dca4c2d991c6d66e1970a112503bba7e"}, - {file = "fonttools-4.54.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:4aa4817f0031206e637d1e685251ac61be64d1adef111060df84fdcbc6ab6c44"}, - {file = "fonttools-4.54.1-cp310-cp310-win32.whl", hash = "sha256:7e3b7d44e18c085fd8c16dcc6f1ad6c61b71ff463636fcb13df7b1b818bd0c02"}, - {file = "fonttools-4.54.1-cp310-cp310-win_amd64.whl", hash = "sha256:dd9cc95b8d6e27d01e1e1f1fae8559ef3c02c76317da650a19047f249acd519d"}, - {file = "fonttools-4.54.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:5419771b64248484299fa77689d4f3aeed643ea6630b2ea750eeab219588ba20"}, - {file = "fonttools-4.54.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:301540e89cf4ce89d462eb23a89464fef50915255ece765d10eee8b2bf9d75b2"}, - {file = "fonttools-4.54.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:76ae5091547e74e7efecc3cbf8e75200bc92daaeb88e5433c5e3e95ea8ce5aa7"}, - {file = "fonttools-4.54.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:82834962b3d7c5ca98cb56001c33cf20eb110ecf442725dc5fdf36d16ed1ab07"}, - {file = "fonttools-4.54.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d26732ae002cc3d2ecab04897bb02ae3f11f06dd7575d1df46acd2f7c012a8d8"}, - {file = "fonttools-4.54.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:58974b4987b2a71ee08ade1e7f47f410c367cdfc5a94fabd599c88165f56213a"}, - {file = "fonttools-4.54.1-cp311-cp311-win32.whl", hash = "sha256:ab774fa225238986218a463f3fe151e04d8c25d7de09df7f0f5fce27b1243dbc"}, - {file = "fonttools-4.54.1-cp311-cp311-win_amd64.whl", hash = "sha256:07e005dc454eee1cc60105d6a29593459a06321c21897f769a281ff2d08939f6"}, - {file = "fonttools-4.54.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:54471032f7cb5fca694b5f1a0aaeba4af6e10ae989df408e0216f7fd6cdc405d"}, - {file = "fonttools-4.54.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8fa92cb248e573daab8d032919623cc309c005086d743afb014c836636166f08"}, - {file = "fonttools-4.54.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0a911591200114969befa7f2cb74ac148bce5a91df5645443371aba6d222e263"}, - {file = "fonttools-4.54.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:93d458c8a6a354dc8b48fc78d66d2a8a90b941f7fec30e94c7ad9982b1fa6bab"}, - {file = "fonttools-4.54.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5eb2474a7c5be8a5331146758debb2669bf5635c021aee00fd7c353558fc659d"}, - {file = "fonttools-4.54.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c9c563351ddc230725c4bdf7d9e1e92cbe6ae8553942bd1fb2b2ff0884e8b714"}, - {file = "fonttools-4.54.1-cp312-cp312-win32.whl", hash = "sha256:fdb062893fd6d47b527d39346e0c5578b7957dcea6d6a3b6794569370013d9ac"}, - {file = "fonttools-4.54.1-cp312-cp312-win_amd64.whl", hash = "sha256:e4564cf40cebcb53f3dc825e85910bf54835e8a8b6880d59e5159f0f325e637e"}, - {file = "fonttools-4.54.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:6e37561751b017cf5c40fce0d90fd9e8274716de327ec4ffb0df957160be3bff"}, - {file = "fonttools-4.54.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:357cacb988a18aace66e5e55fe1247f2ee706e01debc4b1a20d77400354cddeb"}, - {file = "fonttools-4.54.1-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f8e953cc0bddc2beaf3a3c3b5dd9ab7554677da72dfaf46951e193c9653e515a"}, - {file = "fonttools-4.54.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:58d29b9a294573d8319f16f2f79e42428ba9b6480442fa1836e4eb89c4d9d61c"}, - {file = "fonttools-4.54.1-cp313-cp313-win32.whl", hash = "sha256:9ef1b167e22709b46bf8168368b7b5d3efeaaa746c6d39661c1b4405b6352e58"}, - {file = "fonttools-4.54.1-cp313-cp313-win_amd64.whl", hash = "sha256:262705b1663f18c04250bd1242b0515d3bbae177bee7752be67c979b7d47f43d"}, - {file = "fonttools-4.54.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:ed2f80ca07025551636c555dec2b755dd005e2ea8fbeb99fc5cdff319b70b23b"}, - {file = "fonttools-4.54.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:9dc080e5a1c3b2656caff2ac2633d009b3a9ff7b5e93d0452f40cd76d3da3b3c"}, - {file = "fonttools-4.54.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1d152d1be65652fc65e695e5619e0aa0982295a95a9b29b52b85775243c06556"}, - {file = "fonttools-4.54.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8583e563df41fdecef31b793b4dd3af8a9caa03397be648945ad32717a92885b"}, - {file = "fonttools-4.54.1-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:0d1d353ef198c422515a3e974a1e8d5b304cd54a4c2eebcae708e37cd9eeffb1"}, - {file = "fonttools-4.54.1-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:fda582236fee135d4daeca056c8c88ec5f6f6d88a004a79b84a02547c8f57386"}, - {file = "fonttools-4.54.1-cp38-cp38-win32.whl", hash = "sha256:e7d82b9e56716ed32574ee106cabca80992e6bbdcf25a88d97d21f73a0aae664"}, - {file = "fonttools-4.54.1-cp38-cp38-win_amd64.whl", hash = "sha256:ada215fd079e23e060157aab12eba0d66704316547f334eee9ff26f8c0d7b8ab"}, - {file = "fonttools-4.54.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:f5b8a096e649768c2f4233f947cf9737f8dbf8728b90e2771e2497c6e3d21d13"}, - {file = "fonttools-4.54.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4e10d2e0a12e18f4e2dd031e1bf7c3d7017be5c8dbe524d07706179f355c5dac"}, - {file = "fonttools-4.54.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:31c32d7d4b0958600eac75eaf524b7b7cb68d3a8c196635252b7a2c30d80e986"}, - {file = "fonttools-4.54.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c39287f5c8f4a0c5a55daf9eaf9ccd223ea59eed3f6d467133cc727d7b943a55"}, - {file = "fonttools-4.54.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:a7a310c6e0471602fe3bf8efaf193d396ea561486aeaa7adc1f132e02d30c4b9"}, - {file = "fonttools-4.54.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:d3b659d1029946f4ff9b6183984578041b520ce0f8fb7078bb37ec7445806b33"}, - {file = "fonttools-4.54.1-cp39-cp39-win32.whl", hash = "sha256:e96bc94c8cda58f577277d4a71f51c8e2129b8b36fd05adece6320dd3d57de8a"}, - {file = "fonttools-4.54.1-cp39-cp39-win_amd64.whl", hash = "sha256:e8a4b261c1ef91e7188a30571be6ad98d1c6d9fa2427244c545e2fa0a2494dd7"}, - {file = "fonttools-4.54.1-py3-none-any.whl", hash = "sha256:37cddd62d83dc4f72f7c3f3c2bcf2697e89a30efb152079896544a93907733bd"}, - {file = "fonttools-4.54.1.tar.gz", hash = "sha256:957f669d4922f92c171ba01bef7f29410668db09f6c02111e22b2bce446f3285"}, -] - -[package.extras] -all = ["brotli (>=1.0.1)", "brotlicffi (>=0.8.0)", "fs (>=2.2.0,<3)", "lxml (>=4.0)", "lz4 (>=1.7.4.2)", "matplotlib", "munkres", "pycairo", "scipy", "skia-pathops (>=0.5.0)", "sympy", "uharfbuzz (>=0.23.0)", "unicodedata2 (>=15.1.0)", "xattr", "zopfli (>=0.1.4)"] -graphite = ["lz4 (>=1.7.4.2)"] -interpolatable = ["munkres", "pycairo", "scipy"] -lxml = ["lxml (>=4.0)"] -pathops = ["skia-pathops (>=0.5.0)"] -plot = ["matplotlib"] -repacker = ["uharfbuzz (>=0.23.0)"] -symfont = ["sympy"] -type1 = ["xattr"] -ufo = ["fs (>=2.2.0,<3)"] -unicode = ["unicodedata2 (>=15.1.0)"] -woff = ["brotli (>=1.0.1)", "brotlicffi (>=0.8.0)", "zopfli (>=0.1.4)"] - -[[package]] -name = "GDAL" -version = "3.9.2" -description = "GDAL: Geospatial Data Abstraction Library" -category = "main" -optional = false -python-versions = ">=3.7.0" -files = [ - {file = "GDAL-3.9.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0ccdfd68893818a86d2225c1efe6b4a64a6d84676f6fb1e7ec5a4138f70d837b"}, -] - -[package.extras] -numpy = ["numpy (>1.0.0)"] - -[package.source] -type = "url" -url = "https://github.com/girder/large_image_wheels/raw/wheelhouse/GDAL-3.9.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl#sha256=0ccdfd68893818a86d2225c1efe6b4a64a6d84676f6fb1e7ec5a4138f70d837b" - -[[package]] -name = "ipdb" -version = "0.13.13" -description = "IPython-enabled pdb" -category = "main" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -files = [ - {file = "ipdb-0.13.13-py3-none-any.whl", hash = "sha256:45529994741c4ab6d2388bfa5d7b725c2cf7fe9deffabdb8a6113aa5ed449ed4"}, - {file = "ipdb-0.13.13.tar.gz", hash = "sha256:e3ac6018ef05126d442af680aad863006ec19d02290561ac88b8b1c0b0cfc726"}, -] - -[package.dependencies] -decorator = {version = "*", markers = "python_version > \"3.6\""} -ipython = {version = ">=7.31.1", markers = "python_version > \"3.6\""} -tomli = {version = "*", markers = "python_version > \"3.6\" and python_version < \"3.11\""} - -[[package]] -name = "ipython" -version = "8.28.0" -description = "IPython: Productive Interactive Computing" -category = "main" -optional = false -python-versions = ">=3.10" -files = [ - {file = "ipython-8.28.0-py3-none-any.whl", hash = "sha256:530ef1e7bb693724d3cdc37287c80b07ad9b25986c007a53aa1857272dac3f35"}, - {file = "ipython-8.28.0.tar.gz", hash = "sha256:0d0d15ca1e01faeb868ef56bc7ee5a0de5bd66885735682e8a322ae289a13d1a"}, -] - -[package.dependencies] -colorama = {version = "*", markers = "sys_platform == \"win32\""} -decorator = "*" -exceptiongroup = {version = "*", markers = "python_version < \"3.11\""} -jedi = ">=0.16" -matplotlib-inline = "*" -pexpect = {version = ">4.3", markers = "sys_platform != \"win32\" and sys_platform != \"emscripten\""} -prompt-toolkit = ">=3.0.41,<3.1.0" -pygments = ">=2.4.0" -stack-data = "*" -traitlets = ">=5.13.0" -typing-extensions = {version = ">=4.6", markers = "python_version < \"3.12\""} - -[package.extras] -all = ["ipython[black,doc,kernel,matplotlib,nbconvert,nbformat,notebook,parallel,qtconsole]", "ipython[test,test-extra]"] -black = ["black"] -doc = ["docrepr", "exceptiongroup", "intersphinx-registry", "ipykernel", "ipython[test]", "matplotlib", "setuptools (>=18.5)", "sphinx (>=1.3)", "sphinx-rtd-theme", "sphinxcontrib-jquery", "tomli", "typing-extensions"] -kernel = ["ipykernel"] -matplotlib = ["matplotlib"] -nbconvert = ["nbconvert"] -nbformat = ["nbformat"] -notebook = ["ipywidgets", "notebook"] -parallel = ["ipyparallel"] -qtconsole = ["qtconsole"] -test = ["packaging", "pickleshare", "pytest", "pytest-asyncio (<0.22)", "testpath"] -test-extra = ["curio", "ipython[test]", "matplotlib (!=3.2.0)", "nbformat", "numpy (>=1.23)", "pandas", "trio"] - -[[package]] -name = "jedi" -version = "0.19.1" -description = "An autocompletion tool for Python that can be used for text editors." -category = "main" -optional = false -python-versions = ">=3.6" -files = [ - {file = "jedi-0.19.1-py2.py3-none-any.whl", hash = "sha256:e983c654fe5c02867aef4cdfce5a2fbb4a50adc0af145f70504238f18ef5e7e0"}, - {file = "jedi-0.19.1.tar.gz", hash = "sha256:cf0496f3651bc65d7174ac1b7d043eff454892c708a87d1b683e57b569927ffd"}, -] - -[package.dependencies] -parso = ">=0.8.3,<0.9.0" - -[package.extras] -docs = ["Jinja2 (==2.11.3)", "MarkupSafe (==1.1.1)", "Pygments (==2.8.1)", "alabaster (==0.7.12)", "babel (==2.9.1)", "chardet (==4.0.0)", "commonmark (==0.8.1)", "docutils (==0.17.1)", "future (==0.18.2)", "idna (==2.10)", "imagesize (==1.2.0)", "mock (==1.0.1)", "packaging (==20.9)", "pyparsing (==2.4.7)", "pytz (==2021.1)", "readthedocs-sphinx-ext (==2.1.4)", "recommonmark (==0.5.0)", "requests (==2.25.1)", "six (==1.15.0)", "snowballstemmer (==2.1.0)", "sphinx (==1.8.5)", "sphinx-rtd-theme (==0.4.3)", "sphinxcontrib-serializinghtml (==1.1.4)", "sphinxcontrib-websupport (==1.2.4)", "urllib3 (==1.26.4)"] -qa = ["flake8 (==5.0.4)", "mypy (==0.971)", "types-setuptools (==67.2.0.1)"] -testing = ["Django", "attrs", "colorama", "docopt", "pytest (<7.0.0)"] - -[[package]] -name = "kiwisolver" -version = "1.4.7" -description = "A fast implementation of the Cassowary constraint solver" -category = "main" -optional = false -python-versions = ">=3.8" -files = [ - {file = "kiwisolver-1.4.7-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:8a9c83f75223d5e48b0bc9cb1bf2776cf01563e00ade8775ffe13b0b6e1af3a6"}, - {file = "kiwisolver-1.4.7-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:58370b1ffbd35407444d57057b57da5d6549d2d854fa30249771775c63b5fe17"}, - {file = "kiwisolver-1.4.7-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:aa0abdf853e09aff551db11fce173e2177d00786c688203f52c87ad7fcd91ef9"}, - {file = "kiwisolver-1.4.7-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:8d53103597a252fb3ab8b5845af04c7a26d5e7ea8122303dd7a021176a87e8b9"}, - {file = "kiwisolver-1.4.7-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:88f17c5ffa8e9462fb79f62746428dd57b46eb931698e42e990ad63103f35e6c"}, - {file = "kiwisolver-1.4.7-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:88a9ca9c710d598fd75ee5de59d5bda2684d9db36a9f50b6125eaea3969c2599"}, - {file = "kiwisolver-1.4.7-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f4d742cb7af1c28303a51b7a27aaee540e71bb8e24f68c736f6f2ffc82f2bf05"}, - {file = "kiwisolver-1.4.7-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e28c7fea2196bf4c2f8d46a0415c77a1c480cc0724722f23d7410ffe9842c407"}, - {file = "kiwisolver-1.4.7-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:e968b84db54f9d42046cf154e02911e39c0435c9801681e3fc9ce8a3c4130278"}, - {file = "kiwisolver-1.4.7-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:0c18ec74c0472de033e1bebb2911c3c310eef5649133dd0bedf2a169a1b269e5"}, - {file = "kiwisolver-1.4.7-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:8f0ea6da6d393d8b2e187e6a5e3fb81f5862010a40c3945e2c6d12ae45cfb2ad"}, - {file = "kiwisolver-1.4.7-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:f106407dda69ae456dd1227966bf445b157ccc80ba0dff3802bb63f30b74e895"}, - {file = "kiwisolver-1.4.7-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:84ec80df401cfee1457063732d90022f93951944b5b58975d34ab56bb150dfb3"}, - {file = "kiwisolver-1.4.7-cp310-cp310-win32.whl", hash = "sha256:71bb308552200fb2c195e35ef05de12f0c878c07fc91c270eb3d6e41698c3bcc"}, - {file = "kiwisolver-1.4.7-cp310-cp310-win_amd64.whl", hash = "sha256:44756f9fd339de0fb6ee4f8c1696cfd19b2422e0d70b4cefc1cc7f1f64045a8c"}, - {file = "kiwisolver-1.4.7-cp310-cp310-win_arm64.whl", hash = "sha256:78a42513018c41c2ffd262eb676442315cbfe3c44eed82385c2ed043bc63210a"}, - {file = "kiwisolver-1.4.7-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:d2b0e12a42fb4e72d509fc994713d099cbb15ebf1103545e8a45f14da2dfca54"}, - {file = "kiwisolver-1.4.7-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2a8781ac3edc42ea4b90bc23e7d37b665d89423818e26eb6df90698aa2287c95"}, - {file = "kiwisolver-1.4.7-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:46707a10836894b559e04b0fd143e343945c97fd170d69a2d26d640b4e297935"}, - {file = "kiwisolver-1.4.7-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ef97b8df011141c9b0f6caf23b29379f87dd13183c978a30a3c546d2c47314cb"}, - {file = "kiwisolver-1.4.7-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3ab58c12a2cd0fc769089e6d38466c46d7f76aced0a1f54c77652446733d2d02"}, - {file = "kiwisolver-1.4.7-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:803b8e1459341c1bb56d1c5c010406d5edec8a0713a0945851290a7930679b51"}, - {file = "kiwisolver-1.4.7-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f9a9e8a507420fe35992ee9ecb302dab68550dedc0da9e2880dd88071c5fb052"}, - {file = "kiwisolver-1.4.7-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:18077b53dc3bb490e330669a99920c5e6a496889ae8c63b58fbc57c3d7f33a18"}, - {file = "kiwisolver-1.4.7-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6af936f79086a89b3680a280c47ea90b4df7047b5bdf3aa5c524bbedddb9e545"}, - {file = "kiwisolver-1.4.7-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:3abc5b19d24af4b77d1598a585b8a719beb8569a71568b66f4ebe1fb0449460b"}, - {file = "kiwisolver-1.4.7-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:933d4de052939d90afbe6e9d5273ae05fb836cc86c15b686edd4b3560cc0ee36"}, - {file = "kiwisolver-1.4.7-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:65e720d2ab2b53f1f72fb5da5fb477455905ce2c88aaa671ff0a447c2c80e8e3"}, - {file = "kiwisolver-1.4.7-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3bf1ed55088f214ba6427484c59553123fdd9b218a42bbc8c6496d6754b1e523"}, - {file = "kiwisolver-1.4.7-cp311-cp311-win32.whl", hash = "sha256:4c00336b9dd5ad96d0a558fd18a8b6f711b7449acce4c157e7343ba92dd0cf3d"}, - {file = "kiwisolver-1.4.7-cp311-cp311-win_amd64.whl", hash = "sha256:929e294c1ac1e9f615c62a4e4313ca1823ba37326c164ec720a803287c4c499b"}, - {file = "kiwisolver-1.4.7-cp311-cp311-win_arm64.whl", hash = "sha256:e33e8fbd440c917106b237ef1a2f1449dfbb9b6f6e1ce17c94cd6a1e0d438376"}, - {file = "kiwisolver-1.4.7-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:5360cc32706dab3931f738d3079652d20982511f7c0ac5711483e6eab08efff2"}, - {file = "kiwisolver-1.4.7-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:942216596dc64ddb25adb215c3c783215b23626f8d84e8eff8d6d45c3f29f75a"}, - {file = "kiwisolver-1.4.7-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:48b571ecd8bae15702e4f22d3ff6a0f13e54d3d00cd25216d5e7f658242065ee"}, - {file = "kiwisolver-1.4.7-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ad42ba922c67c5f219097b28fae965e10045ddf145d2928bfac2eb2e17673640"}, - {file = "kiwisolver-1.4.7-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:612a10bdae23404a72941a0fc8fa2660c6ea1217c4ce0dbcab8a8f6543ea9e7f"}, - {file = "kiwisolver-1.4.7-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9e838bba3a3bac0fe06d849d29772eb1afb9745a59710762e4ba3f4cb8424483"}, - {file = "kiwisolver-1.4.7-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:22f499f6157236c19f4bbbd472fa55b063db77a16cd74d49afe28992dff8c258"}, - {file = "kiwisolver-1.4.7-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:693902d433cf585133699972b6d7c42a8b9f8f826ebcaf0132ff55200afc599e"}, - {file = "kiwisolver-1.4.7-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:4e77f2126c3e0b0d055f44513ed349038ac180371ed9b52fe96a32aa071a5107"}, - {file = "kiwisolver-1.4.7-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:657a05857bda581c3656bfc3b20e353c232e9193eb167766ad2dc58b56504948"}, - {file = "kiwisolver-1.4.7-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:4bfa75a048c056a411f9705856abfc872558e33c055d80af6a380e3658766038"}, - {file = "kiwisolver-1.4.7-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:34ea1de54beef1c104422d210c47c7d2a4999bdecf42c7b5718fbe59a4cac383"}, - {file = "kiwisolver-1.4.7-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:90da3b5f694b85231cf93586dad5e90e2d71b9428f9aad96952c99055582f520"}, - {file = "kiwisolver-1.4.7-cp312-cp312-win32.whl", hash = "sha256:18e0cca3e008e17fe9b164b55735a325140a5a35faad8de92dd80265cd5eb80b"}, - {file = "kiwisolver-1.4.7-cp312-cp312-win_amd64.whl", hash = "sha256:58cb20602b18f86f83a5c87d3ee1c766a79c0d452f8def86d925e6c60fbf7bfb"}, - {file = "kiwisolver-1.4.7-cp312-cp312-win_arm64.whl", hash = "sha256:f5a8b53bdc0b3961f8b6125e198617c40aeed638b387913bf1ce78afb1b0be2a"}, - {file = "kiwisolver-1.4.7-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:2e6039dcbe79a8e0f044f1c39db1986a1b8071051efba3ee4d74f5b365f5226e"}, - {file = "kiwisolver-1.4.7-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:a1ecf0ac1c518487d9d23b1cd7139a6a65bc460cd101ab01f1be82ecf09794b6"}, - {file = "kiwisolver-1.4.7-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7ab9ccab2b5bd5702ab0803676a580fffa2aa178c2badc5557a84cc943fcf750"}, - {file = "kiwisolver-1.4.7-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f816dd2277f8d63d79f9c8473a79fe54047bc0467754962840782c575522224d"}, - {file = "kiwisolver-1.4.7-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cf8bcc23ceb5a1b624572a1623b9f79d2c3b337c8c455405ef231933a10da379"}, - {file = "kiwisolver-1.4.7-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dea0bf229319828467d7fca8c7c189780aa9ff679c94539eed7532ebe33ed37c"}, - {file = "kiwisolver-1.4.7-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c06a4c7cf15ec739ce0e5971b26c93638730090add60e183530d70848ebdd34"}, - {file = "kiwisolver-1.4.7-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:913983ad2deb14e66d83c28b632fd35ba2b825031f2fa4ca29675e665dfecbe1"}, - {file = "kiwisolver-1.4.7-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5337ec7809bcd0f424c6b705ecf97941c46279cf5ed92311782c7c9c2026f07f"}, - {file = "kiwisolver-1.4.7-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:4c26ed10c4f6fa6ddb329a5120ba3b6db349ca192ae211e882970bfc9d91420b"}, - {file = "kiwisolver-1.4.7-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:c619b101e6de2222c1fcb0531e1b17bbffbe54294bfba43ea0d411d428618c27"}, - {file = "kiwisolver-1.4.7-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:073a36c8273647592ea332e816e75ef8da5c303236ec0167196793eb1e34657a"}, - {file = "kiwisolver-1.4.7-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:3ce6b2b0231bda412463e152fc18335ba32faf4e8c23a754ad50ffa70e4091ee"}, - {file = "kiwisolver-1.4.7-cp313-cp313-win32.whl", hash = "sha256:f4c9aee212bc89d4e13f58be11a56cc8036cabad119259d12ace14b34476fd07"}, - {file = "kiwisolver-1.4.7-cp313-cp313-win_amd64.whl", hash = "sha256:8a3ec5aa8e38fc4c8af308917ce12c536f1c88452ce554027e55b22cbbfbff76"}, - {file = "kiwisolver-1.4.7-cp313-cp313-win_arm64.whl", hash = "sha256:76c8094ac20ec259471ac53e774623eb62e6e1f56cd8690c67ce6ce4fcb05650"}, - {file = "kiwisolver-1.4.7-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:5d5abf8f8ec1f4e22882273c423e16cae834c36856cac348cfbfa68e01c40f3a"}, - {file = "kiwisolver-1.4.7-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:aeb3531b196ef6f11776c21674dba836aeea9d5bd1cf630f869e3d90b16cfade"}, - {file = "kiwisolver-1.4.7-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:b7d755065e4e866a8086c9bdada157133ff466476a2ad7861828e17b6026e22c"}, - {file = "kiwisolver-1.4.7-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:08471d4d86cbaec61f86b217dd938a83d85e03785f51121e791a6e6689a3be95"}, - {file = "kiwisolver-1.4.7-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7bbfcb7165ce3d54a3dfbe731e470f65739c4c1f85bb1018ee912bae139e263b"}, - {file = "kiwisolver-1.4.7-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5d34eb8494bea691a1a450141ebb5385e4b69d38bb8403b5146ad279f4b30fa3"}, - {file = "kiwisolver-1.4.7-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9242795d174daa40105c1d86aba618e8eab7bf96ba8c3ee614da8302a9f95503"}, - {file = "kiwisolver-1.4.7-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:a0f64a48bb81af7450e641e3fe0b0394d7381e342805479178b3d335d60ca7cf"}, - {file = "kiwisolver-1.4.7-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:8e045731a5416357638d1700927529e2b8ab304811671f665b225f8bf8d8f933"}, - {file = "kiwisolver-1.4.7-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:4322872d5772cae7369f8351da1edf255a604ea7087fe295411397d0cfd9655e"}, - {file = "kiwisolver-1.4.7-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:e1631290ee9271dffe3062d2634c3ecac02c83890ada077d225e081aca8aab89"}, - {file = "kiwisolver-1.4.7-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:edcfc407e4eb17e037bca59be0e85a2031a2ac87e4fed26d3e9df88b4165f92d"}, - {file = "kiwisolver-1.4.7-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:4d05d81ecb47d11e7f8932bd8b61b720bf0b41199358f3f5e36d38e28f0532c5"}, - {file = "kiwisolver-1.4.7-cp38-cp38-win32.whl", hash = "sha256:b38ac83d5f04b15e515fd86f312479d950d05ce2368d5413d46c088dda7de90a"}, - {file = "kiwisolver-1.4.7-cp38-cp38-win_amd64.whl", hash = "sha256:d83db7cde68459fc803052a55ace60bea2bae361fc3b7a6d5da07e11954e4b09"}, - {file = "kiwisolver-1.4.7-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:3f9362ecfca44c863569d3d3c033dbe8ba452ff8eed6f6b5806382741a1334bd"}, - {file = "kiwisolver-1.4.7-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:e8df2eb9b2bac43ef8b082e06f750350fbbaf2887534a5be97f6cf07b19d9583"}, - {file = "kiwisolver-1.4.7-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f32d6edbc638cde7652bd690c3e728b25332acbadd7cad670cc4a02558d9c417"}, - {file = "kiwisolver-1.4.7-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:e2e6c39bd7b9372b0be21456caab138e8e69cc0fc1190a9dfa92bd45a1e6e904"}, - {file = "kiwisolver-1.4.7-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:dda56c24d869b1193fcc763f1284b9126550eaf84b88bbc7256e15028f19188a"}, - {file = "kiwisolver-1.4.7-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:79849239c39b5e1fd906556c474d9b0439ea6792b637511f3fe3a41158d89ca8"}, - {file = "kiwisolver-1.4.7-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5e3bc157fed2a4c02ec468de4ecd12a6e22818d4f09cde2c31ee3226ffbefab2"}, - {file = "kiwisolver-1.4.7-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3da53da805b71e41053dc670f9a820d1157aae77b6b944e08024d17bcd51ef88"}, - {file = "kiwisolver-1.4.7-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:8705f17dfeb43139a692298cb6637ee2e59c0194538153e83e9ee0c75c2eddde"}, - {file = "kiwisolver-1.4.7-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:82a5c2f4b87c26bb1a0ef3d16b5c4753434633b83d365cc0ddf2770c93829e3c"}, - {file = "kiwisolver-1.4.7-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:ce8be0466f4c0d585cdb6c1e2ed07232221df101a4c6f28821d2aa754ca2d9e2"}, - {file = "kiwisolver-1.4.7-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:409afdfe1e2e90e6ee7fc896f3df9a7fec8e793e58bfa0d052c8a82f99c37abb"}, - {file = "kiwisolver-1.4.7-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:5b9c3f4ee0b9a439d2415012bd1b1cc2df59e4d6a9939f4d669241d30b414327"}, - {file = "kiwisolver-1.4.7-cp39-cp39-win32.whl", hash = "sha256:a79ae34384df2b615eefca647a2873842ac3b596418032bef9a7283675962644"}, - {file = "kiwisolver-1.4.7-cp39-cp39-win_amd64.whl", hash = "sha256:cf0438b42121a66a3a667de17e779330fc0f20b0d97d59d2f2121e182b0505e4"}, - {file = "kiwisolver-1.4.7-cp39-cp39-win_arm64.whl", hash = "sha256:764202cc7e70f767dab49e8df52c7455e8de0df5d858fa801a11aa0d882ccf3f"}, - {file = "kiwisolver-1.4.7-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:94252291e3fe68001b1dd747b4c0b3be12582839b95ad4d1b641924d68fd4643"}, - {file = "kiwisolver-1.4.7-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:5b7dfa3b546da08a9f622bb6becdb14b3e24aaa30adba66749d38f3cc7ea9706"}, - {file = "kiwisolver-1.4.7-pp310-pypy310_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bd3de6481f4ed8b734da5df134cd5a6a64fe32124fe83dde1e5b5f29fe30b1e6"}, - {file = "kiwisolver-1.4.7-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a91b5f9f1205845d488c928e8570dcb62b893372f63b8b6e98b863ebd2368ff2"}, - {file = "kiwisolver-1.4.7-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:40fa14dbd66b8b8f470d5fc79c089a66185619d31645f9b0773b88b19f7223c4"}, - {file = "kiwisolver-1.4.7-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:eb542fe7933aa09d8d8f9d9097ef37532a7df6497819d16efe4359890a2f417a"}, - {file = "kiwisolver-1.4.7-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:bfa1acfa0c54932d5607e19a2c24646fb4c1ae2694437789129cf099789a3b00"}, - {file = "kiwisolver-1.4.7-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:eee3ea935c3d227d49b4eb85660ff631556841f6e567f0f7bda972df6c2c9935"}, - {file = "kiwisolver-1.4.7-pp38-pypy38_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:f3160309af4396e0ed04db259c3ccbfdc3621b5559b5453075e5de555e1f3a1b"}, - {file = "kiwisolver-1.4.7-pp38-pypy38_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:a17f6a29cf8935e587cc8a4dbfc8368c55edc645283db0ce9801016f83526c2d"}, - {file = "kiwisolver-1.4.7-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:10849fb2c1ecbfae45a693c070e0320a91b35dd4bcf58172c023b994283a124d"}, - {file = "kiwisolver-1.4.7-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:ac542bf38a8a4be2dc6b15248d36315ccc65f0743f7b1a76688ffb6b5129a5c2"}, - {file = "kiwisolver-1.4.7-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:8b01aac285f91ca889c800042c35ad3b239e704b150cfd3382adfc9dcc780e39"}, - {file = "kiwisolver-1.4.7-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:48be928f59a1f5c8207154f935334d374e79f2b5d212826307d072595ad76a2e"}, - {file = "kiwisolver-1.4.7-pp39-pypy39_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f37cfe618a117e50d8c240555331160d73d0411422b59b5ee217843d7b693608"}, - {file = "kiwisolver-1.4.7-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:599b5c873c63a1f6ed7eead644a8a380cfbdf5db91dcb6f85707aaab213b1674"}, - {file = "kiwisolver-1.4.7-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:801fa7802e5cfabe3ab0c81a34c323a319b097dfb5004be950482d882f3d7225"}, - {file = "kiwisolver-1.4.7-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:0c6c43471bc764fad4bc99c5c2d6d16a676b1abf844ca7c8702bdae92df01ee0"}, - {file = "kiwisolver-1.4.7.tar.gz", hash = "sha256:9893ff81bd7107f7b685d3017cc6583daadb4fc26e4a888350df530e41980a60"}, -] - -[[package]] -name = "markdown-it-py" -version = "3.0.0" -description = "Python port of markdown-it. Markdown parsing, done right!" -category = "main" -optional = false -python-versions = ">=3.8" -files = [ - {file = "markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb"}, - {file = "markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1"}, -] - -[package.dependencies] -mdurl = ">=0.1,<1.0" - -[package.extras] -benchmarking = ["psutil", "pytest", "pytest-benchmark"] -code-style = ["pre-commit (>=3.0,<4.0)"] -compare = ["commonmark (>=0.9,<1.0)", "markdown (>=3.4,<4.0)", "mistletoe (>=1.0,<2.0)", "mistune (>=2.0,<3.0)", "panflute (>=2.3,<3.0)"] -linkify = ["linkify-it-py (>=1,<3)"] -plugins = ["mdit-py-plugins"] -profiling = ["gprof2dot"] -rtd = ["jupyter_sphinx", "mdit-py-plugins", "myst-parser", "pyyaml", "sphinx", "sphinx-copybutton", "sphinx-design", "sphinx_book_theme"] -testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions"] - -[[package]] -name = "matplotlib" -version = "3.9.2" -description = "Python plotting package" -category = "main" -optional = false -python-versions = ">=3.9" -files = [ - {file = "matplotlib-3.9.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:9d78bbc0cbc891ad55b4f39a48c22182e9bdaea7fc0e5dbd364f49f729ca1bbb"}, - {file = "matplotlib-3.9.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c375cc72229614632c87355366bdf2570c2dac01ac66b8ad048d2dabadf2d0d4"}, - {file = "matplotlib-3.9.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1d94ff717eb2bd0b58fe66380bd8b14ac35f48a98e7c6765117fe67fb7684e64"}, - {file = "matplotlib-3.9.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ab68d50c06938ef28681073327795c5db99bb4666214d2d5f880ed11aeaded66"}, - {file = "matplotlib-3.9.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:65aacf95b62272d568044531e41de26285d54aec8cb859031f511f84bd8b495a"}, - {file = "matplotlib-3.9.2-cp310-cp310-win_amd64.whl", hash = "sha256:3fd595f34aa8a55b7fc8bf9ebea8aa665a84c82d275190a61118d33fbc82ccae"}, - {file = "matplotlib-3.9.2-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:d8dd059447824eec055e829258ab092b56bb0579fc3164fa09c64f3acd478772"}, - {file = "matplotlib-3.9.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c797dac8bb9c7a3fd3382b16fe8f215b4cf0f22adccea36f1545a6d7be310b41"}, - {file = "matplotlib-3.9.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d719465db13267bcef19ea8954a971db03b9f48b4647e3860e4bc8e6ed86610f"}, - {file = "matplotlib-3.9.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8912ef7c2362f7193b5819d17dae8629b34a95c58603d781329712ada83f9447"}, - {file = "matplotlib-3.9.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:7741f26a58a240f43bee74965c4882b6c93df3e7eb3de160126d8c8f53a6ae6e"}, - {file = "matplotlib-3.9.2-cp311-cp311-win_amd64.whl", hash = "sha256:ae82a14dab96fbfad7965403c643cafe6515e386de723e498cf3eeb1e0b70cc7"}, - {file = "matplotlib-3.9.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:ac43031375a65c3196bee99f6001e7fa5bdfb00ddf43379d3c0609bdca042df9"}, - {file = "matplotlib-3.9.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:be0fc24a5e4531ae4d8e858a1a548c1fe33b176bb13eff7f9d0d38ce5112a27d"}, - {file = "matplotlib-3.9.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bf81de2926c2db243c9b2cbc3917619a0fc85796c6ba4e58f541df814bbf83c7"}, - {file = "matplotlib-3.9.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f6ee45bc4245533111ced13f1f2cace1e7f89d1c793390392a80c139d6cf0e6c"}, - {file = "matplotlib-3.9.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:306c8dfc73239f0e72ac50e5a9cf19cc4e8e331dd0c54f5e69ca8758550f1e1e"}, - {file = "matplotlib-3.9.2-cp312-cp312-win_amd64.whl", hash = "sha256:5413401594cfaff0052f9d8b1aafc6d305b4bd7c4331dccd18f561ff7e1d3bd3"}, - {file = "matplotlib-3.9.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:18128cc08f0d3cfff10b76baa2f296fc28c4607368a8402de61bb3f2eb33c7d9"}, - {file = "matplotlib-3.9.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:4876d7d40219e8ae8bb70f9263bcbe5714415acfdf781086601211335e24f8aa"}, - {file = "matplotlib-3.9.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6d9f07a80deab4bb0b82858a9e9ad53d1382fd122be8cde11080f4e7dfedb38b"}, - {file = "matplotlib-3.9.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f7c0410f181a531ec4e93bbc27692f2c71a15c2da16766f5ba9761e7ae518413"}, - {file = "matplotlib-3.9.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:909645cce2dc28b735674ce0931a4ac94e12f5b13f6bb0b5a5e65e7cea2c192b"}, - {file = "matplotlib-3.9.2-cp313-cp313-win_amd64.whl", hash = "sha256:f32c7410c7f246838a77d6d1eff0c0f87f3cb0e7c4247aebea71a6d5a68cab49"}, - {file = "matplotlib-3.9.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:37e51dd1c2db16ede9cfd7b5cabdfc818b2c6397c83f8b10e0e797501c963a03"}, - {file = "matplotlib-3.9.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:b82c5045cebcecd8496a4d694d43f9cc84aeeb49fe2133e036b207abe73f4d30"}, - {file = "matplotlib-3.9.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f053c40f94bc51bc03832a41b4f153d83f2062d88c72b5e79997072594e97e51"}, - {file = "matplotlib-3.9.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dbe196377a8248972f5cede786d4c5508ed5f5ca4a1e09b44bda889958b33f8c"}, - {file = "matplotlib-3.9.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:5816b1e1fe8c192cbc013f8f3e3368ac56fbecf02fb41b8f8559303f24c5015e"}, - {file = "matplotlib-3.9.2-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:cef2a73d06601437be399908cf13aee74e86932a5ccc6ccdf173408ebc5f6bb2"}, - {file = "matplotlib-3.9.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e0830e188029c14e891fadd99702fd90d317df294c3298aad682739c5533721a"}, - {file = "matplotlib-3.9.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:03ba9c1299c920964e8d3857ba27173b4dbb51ca4bab47ffc2c2ba0eb5e2cbc5"}, - {file = "matplotlib-3.9.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1cd93b91ab47a3616b4d3c42b52f8363b88ca021e340804c6ab2536344fad9ca"}, - {file = "matplotlib-3.9.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:6d1ce5ed2aefcdce11904fc5bbea7d9c21fff3d5f543841edf3dea84451a09ea"}, - {file = "matplotlib-3.9.2-cp39-cp39-win_amd64.whl", hash = "sha256:b2696efdc08648536efd4e1601b5fd491fd47f4db97a5fbfd175549a7365c1b2"}, - {file = "matplotlib-3.9.2-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:d52a3b618cb1cbb769ce2ee1dcdb333c3ab6e823944e9a2d36e37253815f9556"}, - {file = "matplotlib-3.9.2-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:039082812cacd6c6bec8e17a9c1e6baca230d4116d522e81e1f63a74d01d2e21"}, - {file = "matplotlib-3.9.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6758baae2ed64f2331d4fd19be38b7b4eae3ecec210049a26b6a4f3ae1c85dcc"}, - {file = "matplotlib-3.9.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:050598c2b29e0b9832cde72bcf97627bf00262adbc4a54e2b856426bb2ef0697"}, - {file = "matplotlib-3.9.2.tar.gz", hash = "sha256:96ab43906269ca64a6366934106fa01534454a69e471b7bf3d79083981aaab92"}, -] - -[package.dependencies] -contourpy = ">=1.0.1" -cycler = ">=0.10" -fonttools = ">=4.22.0" -kiwisolver = ">=1.3.1" -numpy = ">=1.23" -packaging = ">=20.0" -pillow = ">=8" -pyparsing = ">=2.3.1" -python-dateutil = ">=2.7" - -[package.extras] -dev = ["meson-python (>=0.13.1)", "numpy (>=1.25)", "pybind11 (>=2.6)", "setuptools (>=64)", "setuptools_scm (>=7)"] - -[[package]] -name = "matplotlib-inline" -version = "0.1.7" -description = "Inline Matplotlib backend for Jupyter" -category = "main" -optional = false -python-versions = ">=3.8" -files = [ - {file = "matplotlib_inline-0.1.7-py3-none-any.whl", hash = "sha256:df192d39a4ff8f21b1895d72e6a13f5fcc5099f00fa84384e0ea28c2cc0653ca"}, - {file = "matplotlib_inline-0.1.7.tar.gz", hash = "sha256:8423b23ec666be3d16e16b60bdd8ac4e86e840ebd1dd11a30b9f117f2fa0ab90"}, -] - -[package.dependencies] -traitlets = "*" - -[[package]] -name = "mdurl" -version = "0.1.2" -description = "Markdown URL utilities" -category = "main" -optional = false -python-versions = ">=3.7" -files = [ - {file = "mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8"}, - {file = "mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba"}, -] - -[[package]] -name = "numpy" -version = "2.1.1" -description = "Fundamental package for array computing in Python" -category = "main" -optional = false -python-versions = ">=3.10" -files = [ - {file = "numpy-2.1.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c8a0e34993b510fc19b9a2ce7f31cb8e94ecf6e924a40c0c9dd4f62d0aac47d9"}, - {file = "numpy-2.1.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:7dd86dfaf7c900c0bbdcb8b16e2f6ddf1eb1fe39c6c8cca6e94844ed3152a8fd"}, - {file = "numpy-2.1.1-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:5889dd24f03ca5a5b1e8a90a33b5a0846d8977565e4ae003a63d22ecddf6782f"}, - {file = "numpy-2.1.1-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:59ca673ad11d4b84ceb385290ed0ebe60266e356641428c845b39cd9df6713ab"}, - {file = "numpy-2.1.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:13ce49a34c44b6de5241f0b38b07e44c1b2dcacd9e36c30f9c2fcb1bb5135db7"}, - {file = "numpy-2.1.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:913cc1d311060b1d409e609947fa1b9753701dac96e6581b58afc36b7ee35af6"}, - {file = "numpy-2.1.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:caf5d284ddea7462c32b8d4a6b8af030b6c9fd5332afb70e7414d7fdded4bfd0"}, - {file = "numpy-2.1.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:57eb525e7c2a8fdee02d731f647146ff54ea8c973364f3b850069ffb42799647"}, - {file = "numpy-2.1.1-cp310-cp310-win32.whl", hash = "sha256:9a8e06c7a980869ea67bbf551283bbed2856915f0a792dc32dd0f9dd2fb56728"}, - {file = "numpy-2.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:d10c39947a2d351d6d466b4ae83dad4c37cd6c3cdd6d5d0fa797da56f710a6ae"}, - {file = "numpy-2.1.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0d07841fd284718feffe7dd17a63a2e6c78679b2d386d3e82f44f0108c905550"}, - {file = "numpy-2.1.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b5613cfeb1adfe791e8e681128f5f49f22f3fcaa942255a6124d58ca59d9528f"}, - {file = "numpy-2.1.1-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:0b8cc2715a84b7c3b161f9ebbd942740aaed913584cae9cdc7f8ad5ad41943d0"}, - {file = "numpy-2.1.1-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:b49742cdb85f1f81e4dc1b39dcf328244f4d8d1ded95dea725b316bd2cf18c95"}, - {file = "numpy-2.1.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e8d5f8a8e3bc87334f025194c6193e408903d21ebaeb10952264943a985066ca"}, - {file = "numpy-2.1.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d51fc141ddbe3f919e91a096ec739f49d686df8af254b2053ba21a910ae518bf"}, - {file = "numpy-2.1.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:98ce7fb5b8063cfdd86596b9c762bf2b5e35a2cdd7e967494ab78a1fa7f8b86e"}, - {file = "numpy-2.1.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:24c2ad697bd8593887b019817ddd9974a7f429c14a5469d7fad413f28340a6d2"}, - {file = "numpy-2.1.1-cp311-cp311-win32.whl", hash = "sha256:397bc5ce62d3fb73f304bec332171535c187e0643e176a6e9421a6e3eacef06d"}, - {file = "numpy-2.1.1-cp311-cp311-win_amd64.whl", hash = "sha256:ae8ce252404cdd4de56dcfce8b11eac3c594a9c16c231d081fb705cf23bd4d9e"}, - {file = "numpy-2.1.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:7c803b7934a7f59563db459292e6aa078bb38b7ab1446ca38dd138646a38203e"}, - {file = "numpy-2.1.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6435c48250c12f001920f0751fe50c0348f5f240852cfddc5e2f97e007544cbe"}, - {file = "numpy-2.1.1-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:3269c9eb8745e8d975980b3a7411a98976824e1fdef11f0aacf76147f662b15f"}, - {file = "numpy-2.1.1-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:fac6e277a41163d27dfab5f4ec1f7a83fac94e170665a4a50191b545721c6521"}, - {file = "numpy-2.1.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fcd8f556cdc8cfe35e70efb92463082b7f43dd7e547eb071ffc36abc0ca4699b"}, - {file = "numpy-2.1.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2b9cd92c8f8e7b313b80e93cedc12c0112088541dcedd9197b5dee3738c1201"}, - {file = "numpy-2.1.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:afd9c680df4de71cd58582b51e88a61feed4abcc7530bcd3d48483f20fc76f2a"}, - {file = "numpy-2.1.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8661c94e3aad18e1ea17a11f60f843a4933ccaf1a25a7c6a9182af70610b2313"}, - {file = "numpy-2.1.1-cp312-cp312-win32.whl", hash = "sha256:950802d17a33c07cba7fd7c3dcfa7d64705509206be1606f196d179e539111ed"}, - {file = "numpy-2.1.1-cp312-cp312-win_amd64.whl", hash = "sha256:3fc5eabfc720db95d68e6646e88f8b399bfedd235994016351b1d9e062c4b270"}, - {file = "numpy-2.1.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:046356b19d7ad1890c751b99acad5e82dc4a02232013bd9a9a712fddf8eb60f5"}, - {file = "numpy-2.1.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:6e5a9cb2be39350ae6c8f79410744e80154df658d5bea06e06e0ac5bb75480d5"}, - {file = "numpy-2.1.1-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:d4c57b68c8ef5e1ebf47238e99bf27657511ec3f071c465f6b1bccbef12d4136"}, - {file = "numpy-2.1.1-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:8ae0fd135e0b157365ac7cc31fff27f07a5572bdfc38f9c2d43b2aff416cc8b0"}, - {file = "numpy-2.1.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:981707f6b31b59c0c24bcda52e5605f9701cb46da4b86c2e8023656ad3e833cb"}, - {file = "numpy-2.1.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2ca4b53e1e0b279142113b8c5eb7d7a877e967c306edc34f3b58e9be12fda8df"}, - {file = "numpy-2.1.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:e097507396c0be4e547ff15b13dc3866f45f3680f789c1a1301b07dadd3fbc78"}, - {file = "numpy-2.1.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f7506387e191fe8cdb267f912469a3cccc538ab108471291636a96a54e599556"}, - {file = "numpy-2.1.1-cp313-cp313-win32.whl", hash = "sha256:251105b7c42abe40e3a689881e1793370cc9724ad50d64b30b358bbb3a97553b"}, - {file = "numpy-2.1.1-cp313-cp313-win_amd64.whl", hash = "sha256:f212d4f46b67ff604d11fff7cc62d36b3e8714edf68e44e9760e19be38c03eb0"}, - {file = "numpy-2.1.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:920b0911bb2e4414c50e55bd658baeb78281a47feeb064ab40c2b66ecba85553"}, - {file = "numpy-2.1.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:bab7c09454460a487e631ffc0c42057e3d8f2a9ddccd1e60c7bb8ed774992480"}, - {file = "numpy-2.1.1-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:cea427d1350f3fd0d2818ce7350095c1a2ee33e30961d2f0fef48576ddbbe90f"}, - {file = "numpy-2.1.1-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:e30356d530528a42eeba51420ae8bf6c6c09559051887196599d96ee5f536468"}, - {file = "numpy-2.1.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e8dfa9e94fc127c40979c3eacbae1e61fda4fe71d84869cc129e2721973231ef"}, - {file = "numpy-2.1.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:910b47a6d0635ec1bd53b88f86120a52bf56dcc27b51f18c7b4a2e2224c29f0f"}, - {file = "numpy-2.1.1-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:13cc11c00000848702322af4de0147ced365c81d66053a67c2e962a485b3717c"}, - {file = "numpy-2.1.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:53e27293b3a2b661c03f79aa51c3987492bd4641ef933e366e0f9f6c9bf257ec"}, - {file = "numpy-2.1.1-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:7be6a07520b88214ea85d8ac8b7d6d8a1839b0b5cb87412ac9f49fa934eb15d5"}, - {file = "numpy-2.1.1-pp310-pypy310_pp73-macosx_14_0_x86_64.whl", hash = "sha256:52ac2e48f5ad847cd43c4755520a2317f3380213493b9d8a4c5e37f3b87df504"}, - {file = "numpy-2.1.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:50a95ca3560a6058d6ea91d4629a83a897ee27c00630aed9d933dff191f170cd"}, - {file = "numpy-2.1.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:99f4a9ee60eed1385a86e82288971a51e71df052ed0b2900ed30bc840c0f2e39"}, - {file = "numpy-2.1.1.tar.gz", hash = "sha256:d0cf7d55b1051387807405b3898efafa862997b4cba8aa5dbe657be794afeafd"}, -] - -[[package]] -name = "opencv-python" -version = "4.10.0.84" -description = "Wrapper package for OpenCV python bindings." -category = "main" -optional = false -python-versions = ">=3.6" -files = [ - {file = "opencv-python-4.10.0.84.tar.gz", hash = "sha256:72d234e4582e9658ffea8e9cae5b63d488ad06994ef12d81dc303b17472f3526"}, - {file = "opencv_python-4.10.0.84-cp37-abi3-macosx_11_0_arm64.whl", hash = "sha256:fc182f8f4cda51b45f01c64e4cbedfc2f00aff799debebc305d8d0210c43f251"}, - {file = "opencv_python-4.10.0.84-cp37-abi3-macosx_12_0_x86_64.whl", hash = "sha256:71e575744f1d23f79741450254660442785f45a0797212852ee5199ef12eed98"}, - {file = "opencv_python-4.10.0.84-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:09a332b50488e2dda866a6c5573ee192fe3583239fb26ff2f7f9ceb0bc119ea6"}, - {file = "opencv_python-4.10.0.84-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9ace140fc6d647fbe1c692bcb2abce768973491222c067c131d80957c595b71f"}, - {file = "opencv_python-4.10.0.84-cp37-abi3-win32.whl", hash = "sha256:2db02bb7e50b703f0a2d50c50ced72e95c574e1e5a0bb35a8a86d0b35c98c236"}, - {file = "opencv_python-4.10.0.84-cp37-abi3-win_amd64.whl", hash = "sha256:32dbbd94c26f611dc5cc6979e6b7aa1f55a64d6b463cc1dcd3c95505a63e48fe"}, -] - -[package.dependencies] -numpy = [ - {version = ">=1.21.2", markers = "python_version >= \"3.10\""}, - {version = ">=1.21.4", markers = "python_version >= \"3.10\" and platform_system == \"Darwin\""}, - {version = ">=1.23.5", markers = "python_version >= \"3.11\""}, - {version = ">=1.19.3", markers = "python_version >= \"3.6\" and platform_system == \"Linux\" and platform_machine == \"aarch64\" or python_version >= \"3.9\""}, - {version = ">=1.17.0", markers = "python_version >= \"3.7\""}, - {version = ">=1.17.3", markers = "python_version >= \"3.8\""}, - {version = ">=1.26.0", markers = "python_version >= \"3.12\""}, -] - -[[package]] -name = "packaging" -version = "24.1" -description = "Core utilities for Python packages" -category = "main" -optional = false -python-versions = ">=3.8" -files = [ - {file = "packaging-24.1-py3-none-any.whl", hash = "sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124"}, - {file = "packaging-24.1.tar.gz", hash = "sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002"}, -] - -[[package]] -name = "parso" -version = "0.8.4" -description = "A Python Parser" -category = "main" -optional = false -python-versions = ">=3.6" -files = [ - {file = "parso-0.8.4-py2.py3-none-any.whl", hash = "sha256:a418670a20291dacd2dddc80c377c5c3791378ee1e8d12bffc35420643d43f18"}, - {file = "parso-0.8.4.tar.gz", hash = "sha256:eb3a7b58240fb99099a345571deecc0f9540ea5f4dd2fe14c2a99d6b281ab92d"}, -] - -[package.extras] -qa = ["flake8 (==5.0.4)", "mypy (==0.971)", "types-setuptools (==67.2.0.1)"] -testing = ["docopt", "pytest"] - -[[package]] -name = "pexpect" -version = "4.9.0" -description = "Pexpect allows easy control of interactive console applications." -category = "main" -optional = false -python-versions = "*" -files = [ - {file = "pexpect-4.9.0-py2.py3-none-any.whl", hash = "sha256:7236d1e080e4936be2dc3e326cec0af72acf9212a7e1d060210e70a47e253523"}, - {file = "pexpect-4.9.0.tar.gz", hash = "sha256:ee7d41123f3c9911050ea2c2dac107568dc43b2d3b0c7557a33212c398ead30f"}, -] - -[package.dependencies] -ptyprocess = ">=0.5" - -[[package]] -name = "pillow" -version = "10.4.0" -description = "Python Imaging Library (Fork)" -category = "main" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pillow-10.4.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:4d9667937cfa347525b319ae34375c37b9ee6b525440f3ef48542fcf66f2731e"}, - {file = "pillow-10.4.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:543f3dc61c18dafb755773efc89aae60d06b6596a63914107f75459cf984164d"}, - {file = "pillow-10.4.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7928ecbf1ece13956b95d9cbcfc77137652b02763ba384d9ab508099a2eca856"}, - {file = "pillow-10.4.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e4d49b85c4348ea0b31ea63bc75a9f3857869174e2bf17e7aba02945cd218e6f"}, - {file = "pillow-10.4.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:6c762a5b0997f5659a5ef2266abc1d8851ad7749ad9a6a5506eb23d314e4f46b"}, - {file = "pillow-10.4.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:a985e028fc183bf12a77a8bbf36318db4238a3ded7fa9df1b9a133f1cb79f8fc"}, - {file = "pillow-10.4.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:812f7342b0eee081eaec84d91423d1b4650bb9828eb53d8511bcef8ce5aecf1e"}, - {file = "pillow-10.4.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:ac1452d2fbe4978c2eec89fb5a23b8387aba707ac72810d9490118817d9c0b46"}, - {file = "pillow-10.4.0-cp310-cp310-win32.whl", hash = "sha256:bcd5e41a859bf2e84fdc42f4edb7d9aba0a13d29a2abadccafad99de3feff984"}, - {file = "pillow-10.4.0-cp310-cp310-win_amd64.whl", hash = "sha256:ecd85a8d3e79cd7158dec1c9e5808e821feea088e2f69a974db5edf84dc53141"}, - {file = "pillow-10.4.0-cp310-cp310-win_arm64.whl", hash = "sha256:ff337c552345e95702c5fde3158acb0625111017d0e5f24bf3acdb9cc16b90d1"}, - {file = "pillow-10.4.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:0a9ec697746f268507404647e531e92889890a087e03681a3606d9b920fbee3c"}, - {file = "pillow-10.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:dfe91cb65544a1321e631e696759491ae04a2ea11d36715eca01ce07284738be"}, - {file = "pillow-10.4.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5dc6761a6efc781e6a1544206f22c80c3af4c8cf461206d46a1e6006e4429ff3"}, - {file = "pillow-10.4.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5e84b6cc6a4a3d76c153a6b19270b3526a5a8ed6b09501d3af891daa2a9de7d6"}, - {file = "pillow-10.4.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:bbc527b519bd3aa9d7f429d152fea69f9ad37c95f0b02aebddff592688998abe"}, - {file = "pillow-10.4.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:76a911dfe51a36041f2e756b00f96ed84677cdeb75d25c767f296c1c1eda1319"}, - {file = "pillow-10.4.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:59291fb29317122398786c2d44427bbd1a6d7ff54017075b22be9d21aa59bd8d"}, - {file = "pillow-10.4.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:416d3a5d0e8cfe4f27f574362435bc9bae57f679a7158e0096ad2beb427b8696"}, - {file = "pillow-10.4.0-cp311-cp311-win32.whl", hash = "sha256:7086cc1d5eebb91ad24ded9f58bec6c688e9f0ed7eb3dbbf1e4800280a896496"}, - {file = "pillow-10.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:cbed61494057c0f83b83eb3a310f0bf774b09513307c434d4366ed64f4128a91"}, - {file = "pillow-10.4.0-cp311-cp311-win_arm64.whl", hash = "sha256:f5f0c3e969c8f12dd2bb7e0b15d5c468b51e5017e01e2e867335c81903046a22"}, - {file = "pillow-10.4.0-cp312-cp312-macosx_10_10_x86_64.whl", hash = "sha256:673655af3eadf4df6b5457033f086e90299fdd7a47983a13827acf7459c15d94"}, - {file = "pillow-10.4.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:866b6942a92f56300012f5fbac71f2d610312ee65e22f1aa2609e491284e5597"}, - {file = "pillow-10.4.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:29dbdc4207642ea6aad70fbde1a9338753d33fb23ed6956e706936706f52dd80"}, - {file = "pillow-10.4.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bf2342ac639c4cf38799a44950bbc2dfcb685f052b9e262f446482afaf4bffca"}, - {file = "pillow-10.4.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:f5b92f4d70791b4a67157321c4e8225d60b119c5cc9aee8ecf153aace4aad4ef"}, - {file = "pillow-10.4.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:86dcb5a1eb778d8b25659d5e4341269e8590ad6b4e8b44d9f4b07f8d136c414a"}, - {file = "pillow-10.4.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:780c072c2e11c9b2c7ca37f9a2ee8ba66f44367ac3e5c7832afcfe5104fd6d1b"}, - {file = "pillow-10.4.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:37fb69d905be665f68f28a8bba3c6d3223c8efe1edf14cc4cfa06c241f8c81d9"}, - {file = "pillow-10.4.0-cp312-cp312-win32.whl", hash = "sha256:7dfecdbad5c301d7b5bde160150b4db4c659cee2b69589705b6f8a0c509d9f42"}, - {file = "pillow-10.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:1d846aea995ad352d4bdcc847535bd56e0fd88d36829d2c90be880ef1ee4668a"}, - {file = "pillow-10.4.0-cp312-cp312-win_arm64.whl", hash = "sha256:e553cad5179a66ba15bb18b353a19020e73a7921296a7979c4a2b7f6a5cd57f9"}, - {file = "pillow-10.4.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8bc1a764ed8c957a2e9cacf97c8b2b053b70307cf2996aafd70e91a082e70df3"}, - {file = "pillow-10.4.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:6209bb41dc692ddfee4942517c19ee81b86c864b626dbfca272ec0f7cff5d9fb"}, - {file = "pillow-10.4.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bee197b30783295d2eb680b311af15a20a8b24024a19c3a26431ff83eb8d1f70"}, - {file = "pillow-10.4.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1ef61f5dd14c300786318482456481463b9d6b91ebe5ef12f405afbba77ed0be"}, - {file = "pillow-10.4.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:297e388da6e248c98bc4a02e018966af0c5f92dfacf5a5ca22fa01cb3179bca0"}, - {file = "pillow-10.4.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:e4db64794ccdf6cb83a59d73405f63adbe2a1887012e308828596100a0b2f6cc"}, - {file = "pillow-10.4.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:bd2880a07482090a3bcb01f4265f1936a903d70bc740bfcb1fd4e8a2ffe5cf5a"}, - {file = "pillow-10.4.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4b35b21b819ac1dbd1233317adeecd63495f6babf21b7b2512d244ff6c6ce309"}, - {file = "pillow-10.4.0-cp313-cp313-win32.whl", hash = "sha256:551d3fd6e9dc15e4c1eb6fc4ba2b39c0c7933fa113b220057a34f4bb3268a060"}, - {file = "pillow-10.4.0-cp313-cp313-win_amd64.whl", hash = "sha256:030abdbe43ee02e0de642aee345efa443740aa4d828bfe8e2eb11922ea6a21ea"}, - {file = "pillow-10.4.0-cp313-cp313-win_arm64.whl", hash = "sha256:5b001114dd152cfd6b23befeb28d7aee43553e2402c9f159807bf55f33af8a8d"}, - {file = "pillow-10.4.0-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:8d4d5063501b6dd4024b8ac2f04962d661222d120381272deea52e3fc52d3736"}, - {file = "pillow-10.4.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7c1ee6f42250df403c5f103cbd2768a28fe1a0ea1f0f03fe151c8741e1469c8b"}, - {file = "pillow-10.4.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b15e02e9bb4c21e39876698abf233c8c579127986f8207200bc8a8f6bb27acf2"}, - {file = "pillow-10.4.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7a8d4bade9952ea9a77d0c3e49cbd8b2890a399422258a77f357b9cc9be8d680"}, - {file = "pillow-10.4.0-cp38-cp38-manylinux_2_28_aarch64.whl", hash = "sha256:43efea75eb06b95d1631cb784aa40156177bf9dd5b4b03ff38979e048258bc6b"}, - {file = "pillow-10.4.0-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:950be4d8ba92aca4b2bb0741285a46bfae3ca699ef913ec8416c1b78eadd64cd"}, - {file = "pillow-10.4.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:d7480af14364494365e89d6fddc510a13e5a2c3584cb19ef65415ca57252fb84"}, - {file = "pillow-10.4.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:73664fe514b34c8f02452ffb73b7a92c6774e39a647087f83d67f010eb9a0cf0"}, - {file = "pillow-10.4.0-cp38-cp38-win32.whl", hash = "sha256:e88d5e6ad0d026fba7bdab8c3f225a69f063f116462c49892b0149e21b6c0a0e"}, - {file = "pillow-10.4.0-cp38-cp38-win_amd64.whl", hash = "sha256:5161eef006d335e46895297f642341111945e2c1c899eb406882a6c61a4357ab"}, - {file = "pillow-10.4.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:0ae24a547e8b711ccaaf99c9ae3cd975470e1a30caa80a6aaee9a2f19c05701d"}, - {file = "pillow-10.4.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:298478fe4f77a4408895605f3482b6cc6222c018b2ce565c2b6b9c354ac3229b"}, - {file = "pillow-10.4.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:134ace6dc392116566980ee7436477d844520a26a4b1bd4053f6f47d096997fd"}, - {file = "pillow-10.4.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:930044bb7679ab003b14023138b50181899da3f25de50e9dbee23b61b4de2126"}, - {file = "pillow-10.4.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:c76e5786951e72ed3686e122d14c5d7012f16c8303a674d18cdcd6d89557fc5b"}, - {file = "pillow-10.4.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:b2724fdb354a868ddf9a880cb84d102da914e99119211ef7ecbdc613b8c96b3c"}, - {file = "pillow-10.4.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:dbc6ae66518ab3c5847659e9988c3b60dc94ffb48ef9168656e0019a93dbf8a1"}, - {file = "pillow-10.4.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:06b2f7898047ae93fad74467ec3d28fe84f7831370e3c258afa533f81ef7f3df"}, - {file = "pillow-10.4.0-cp39-cp39-win32.whl", hash = "sha256:7970285ab628a3779aecc35823296a7869f889b8329c16ad5a71e4901a3dc4ef"}, - {file = "pillow-10.4.0-cp39-cp39-win_amd64.whl", hash = "sha256:961a7293b2457b405967af9c77dcaa43cc1a8cd50d23c532e62d48ab6cdd56f5"}, - {file = "pillow-10.4.0-cp39-cp39-win_arm64.whl", hash = "sha256:32cda9e3d601a52baccb2856b8ea1fc213c90b340c542dcef77140dfa3278a9e"}, - {file = "pillow-10.4.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:5b4815f2e65b30f5fbae9dfffa8636d992d49705723fe86a3661806e069352d4"}, - {file = "pillow-10.4.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:8f0aef4ef59694b12cadee839e2ba6afeab89c0f39a3adc02ed51d109117b8da"}, - {file = "pillow-10.4.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9f4727572e2918acaa9077c919cbbeb73bd2b3ebcfe033b72f858fc9fbef0026"}, - {file = "pillow-10.4.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ff25afb18123cea58a591ea0244b92eb1e61a1fd497bf6d6384f09bc3262ec3e"}, - {file = "pillow-10.4.0-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:dc3e2db6ba09ffd7d02ae9141cfa0ae23393ee7687248d46a7507b75d610f4f5"}, - {file = "pillow-10.4.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:02a2be69f9c9b8c1e97cf2713e789d4e398c751ecfd9967c18d0ce304efbf885"}, - {file = "pillow-10.4.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:0755ffd4a0c6f267cccbae2e9903d95477ca2f77c4fcf3a3a09570001856c8a5"}, - {file = "pillow-10.4.0-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:a02364621fe369e06200d4a16558e056fe2805d3468350df3aef21e00d26214b"}, - {file = "pillow-10.4.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:1b5dea9831a90e9d0721ec417a80d4cbd7022093ac38a568db2dd78363b00908"}, - {file = "pillow-10.4.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9b885f89040bb8c4a1573566bbb2f44f5c505ef6e74cec7ab9068c900047f04b"}, - {file = "pillow-10.4.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:87dd88ded2e6d74d31e1e0a99a726a6765cda32d00ba72dc37f0651f306daaa8"}, - {file = "pillow-10.4.0-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:2db98790afc70118bd0255c2eeb465e9767ecf1f3c25f9a1abb8ffc8cfd1fe0a"}, - {file = "pillow-10.4.0-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:f7baece4ce06bade126fb84b8af1c33439a76d8a6fd818970215e0560ca28c27"}, - {file = "pillow-10.4.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:cfdd747216947628af7b259d274771d84db2268ca062dd5faf373639d00113a3"}, - {file = "pillow-10.4.0.tar.gz", hash = "sha256:166c1cd4d24309b30d61f79f4a9114b7b2313d7450912277855ff5dfd7cd4a06"}, -] - -[package.extras] -docs = ["furo", "olefile", "sphinx (>=7.3)", "sphinx-copybutton", "sphinx-inline-tabs", "sphinxext-opengraph"] -fpx = ["olefile"] -mic = ["olefile"] -tests = ["check-manifest", "coverage", "defusedxml", "markdown2", "olefile", "packaging", "pyroma", "pytest", "pytest-cov", "pytest-timeout"] -typing = ["typing-extensions"] -xmp = ["defusedxml"] - -[[package]] -name = "prompt-toolkit" -version = "3.0.48" -description = "Library for building powerful interactive command lines in Python" -category = "main" -optional = false -python-versions = ">=3.7.0" -files = [ - {file = "prompt_toolkit-3.0.48-py3-none-any.whl", hash = "sha256:f49a827f90062e411f1ce1f854f2aedb3c23353244f8108b89283587397ac10e"}, - {file = "prompt_toolkit-3.0.48.tar.gz", hash = "sha256:d6623ab0477a80df74e646bdbc93621143f5caf104206aa29294d53de1a03d90"}, -] - -[package.dependencies] -wcwidth = "*" - -[[package]] -name = "ptyprocess" -version = "0.7.0" -description = "Run a subprocess in a pseudo terminal" -category = "main" -optional = false -python-versions = "*" -files = [ - {file = "ptyprocess-0.7.0-py2.py3-none-any.whl", hash = "sha256:4b41f3967fce3af57cc7e94b888626c18bf37a083e3651ca8feeb66d492fef35"}, - {file = "ptyprocess-0.7.0.tar.gz", hash = "sha256:5c5d0a3b48ceee0b48485e0c26037c0acd7d29765ca3fbb5cb3831d347423220"}, -] - -[[package]] -name = "pure-eval" -version = "0.2.3" -description = "Safely evaluate AST nodes without side effects" -category = "main" -optional = false -python-versions = "*" -files = [ - {file = "pure_eval-0.2.3-py3-none-any.whl", hash = "sha256:1db8e35b67b3d218d818ae653e27f06c3aa420901fa7b081ca98cbedc874e0d0"}, - {file = "pure_eval-0.2.3.tar.gz", hash = "sha256:5f4e983f40564c576c7c8635ae88db5956bb2229d7e9237d03b3c0b0190eaf42"}, -] - -[package.extras] -tests = ["pytest"] - -[[package]] -name = "pygeodesy" -version = "24.9.29" -description = "Pure Python geodesy tools" -category = "main" -optional = false -python-versions = "*" -files = [ - {file = "PyGeodesy-24.9.29-py2.py3-none-any.whl", hash = "sha256:754f2cf603bb54315257e8eb619c286457cea0ae64d802abb6c0088155dc45c4"}, - {file = "PyGeodesy-24.9.29.zip", hash = "sha256:c28ce86d450f7a7e8d5c432cec15cc7c01fded6b4e240aa0b07d20c46cc84038"}, -] - -[[package]] -name = "pygments" -version = "2.18.0" -description = "Pygments is a syntax highlighting package written in Python." -category = "main" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pygments-2.18.0-py3-none-any.whl", hash = "sha256:b8e6aca0523f3ab76fee51799c488e38782ac06eafcf95e7ba832985c8e7b13a"}, - {file = "pygments-2.18.0.tar.gz", hash = "sha256:786ff802f32e91311bff3889f6e9a86e81505fe99f2735bb6d60ae0c5004f199"}, -] - -[package.extras] -windows-terminal = ["colorama (>=0.4.6)"] - -[[package]] -name = "pyparsing" -version = "3.1.4" -description = "pyparsing module - Classes and methods to define and execute parsing grammars" -category = "main" -optional = false -python-versions = ">=3.6.8" -files = [ - {file = "pyparsing-3.1.4-py3-none-any.whl", hash = "sha256:a6a7ee4235a3f944aa1fa2249307708f893fe5717dc603503c6c7969c070fb7c"}, - {file = "pyparsing-3.1.4.tar.gz", hash = "sha256:f86ec8d1a83f11977c9a6ea7598e8c27fc5cddfa5b07ea2241edbbde1d7bc032"}, -] - -[package.extras] -diagrams = ["jinja2", "railroad-diagrams"] - -[[package]] -name = "pyshp" -version = "2.3.1" -description = "Pure Python read/write support for ESRI Shapefile format" -category = "main" -optional = false -python-versions = ">=2.7" -files = [ - {file = "pyshp-2.3.1-py2.py3-none-any.whl", hash = "sha256:67024c0ccdc352ba5db777c4e968483782dfa78f8e200672a90d2d30fd8b7b49"}, - {file = "pyshp-2.3.1.tar.gz", hash = "sha256:4caec82fd8dd096feba8217858068bacb2a3b5950f43c048c6dc32a3489d5af1"}, -] - -[[package]] -name = "python-dateutil" -version = "2.9.0.post0" -description = "Extensions to the standard Python datetime module" -category = "main" -optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" -files = [ - {file = "python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3"}, - {file = "python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427"}, -] - -[package.dependencies] -six = ">=1.5" - -[[package]] -name = "pytz" -version = "2024.2" -description = "World timezone definitions, modern and historical" -category = "main" -optional = false -python-versions = "*" -files = [ - {file = "pytz-2024.2-py2.py3-none-any.whl", hash = "sha256:31c7c1817eb7fae7ca4b8c7ee50c72f93aa2dd863de768e1ef4245d426aa0725"}, - {file = "pytz-2024.2.tar.gz", hash = "sha256:2aa355083c50a0f93fa581709deac0c9ad65cca8a9e9beac660adcbd493c798a"}, -] - -[[package]] -name = "pyyaml" -version = "6.0.2" -description = "YAML parser and emitter for Python" -category = "main" -optional = false -python-versions = ">=3.8" -files = [ - {file = "PyYAML-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086"}, - {file = "PyYAML-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf"}, - {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8824b5a04a04a047e72eea5cec3bc266db09e35de6bdfe34c9436ac5ee27d237"}, - {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c36280e6fb8385e520936c3cb3b8042851904eba0e58d277dca80a5cfed590b"}, - {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec031d5d2feb36d1d1a24380e4db6d43695f3748343d99434e6f5f9156aaa2ed"}, - {file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:936d68689298c36b53b29f23c6dbb74de12b4ac12ca6cfe0e047bedceea56180"}, - {file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:23502f431948090f597378482b4812b0caae32c22213aecf3b55325e049a6c68"}, - {file = "PyYAML-6.0.2-cp310-cp310-win32.whl", hash = "sha256:2e99c6826ffa974fe6e27cdb5ed0021786b03fc98e5ee3c5bfe1fd5015f42b99"}, - {file = "PyYAML-6.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:a4d3091415f010369ae4ed1fc6b79def9416358877534caf6a0fdd2146c87a3e"}, - {file = "PyYAML-6.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774"}, - {file = "PyYAML-6.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee"}, - {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c"}, - {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317"}, - {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85"}, - {file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4"}, - {file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e"}, - {file = "PyYAML-6.0.2-cp311-cp311-win32.whl", hash = "sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5"}, - {file = "PyYAML-6.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44"}, - {file = "PyYAML-6.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab"}, - {file = "PyYAML-6.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725"}, - {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5"}, - {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425"}, - {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476"}, - {file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48"}, - {file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b"}, - {file = "PyYAML-6.0.2-cp312-cp312-win32.whl", hash = "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4"}, - {file = "PyYAML-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8"}, - {file = "PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba"}, - {file = "PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1"}, - {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133"}, - {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484"}, - {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5"}, - {file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc"}, - {file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652"}, - {file = "PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183"}, - {file = "PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563"}, - {file = "PyYAML-6.0.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:24471b829b3bf607e04e88d79542a9d48bb037c2267d7927a874e6c205ca7e9a"}, - {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7fded462629cfa4b685c5416b949ebad6cec74af5e2d42905d41e257e0869f5"}, - {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d84a1718ee396f54f3a086ea0a66d8e552b2ab2017ef8b420e92edbc841c352d"}, - {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9056c1ecd25795207ad294bcf39f2db3d845767be0ea6e6a34d856f006006083"}, - {file = "PyYAML-6.0.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:82d09873e40955485746739bcb8b4586983670466c23382c19cffecbf1fd8706"}, - {file = "PyYAML-6.0.2-cp38-cp38-win32.whl", hash = "sha256:43fa96a3ca0d6b1812e01ced1044a003533c47f6ee8aca31724f78e93ccc089a"}, - {file = "PyYAML-6.0.2-cp38-cp38-win_amd64.whl", hash = "sha256:01179a4a8559ab5de078078f37e5c1a30d76bb88519906844fd7bdea1b7729ff"}, - {file = "PyYAML-6.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:688ba32a1cffef67fd2e9398a2efebaea461578b0923624778664cc1c914db5d"}, - {file = "PyYAML-6.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a8786accb172bd8afb8be14490a16625cbc387036876ab6ba70912730faf8e1f"}, - {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8e03406cac8513435335dbab54c0d385e4a49e4945d2909a581c83647ca0290"}, - {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f753120cb8181e736c57ef7636e83f31b9c0d1722c516f7e86cf15b7aa57ff12"}, - {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b1fdb9dc17f5a7677423d508ab4f243a726dea51fa5e70992e59a7411c89d19"}, - {file = "PyYAML-6.0.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0b69e4ce7a131fe56b7e4d770c67429700908fc0752af059838b1cfb41960e4e"}, - {file = "PyYAML-6.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a9f8c2e67970f13b16084e04f134610fd1d374bf477b17ec1599185cf611d725"}, - {file = "PyYAML-6.0.2-cp39-cp39-win32.whl", hash = "sha256:6395c297d42274772abc367baaa79683958044e5d3835486c16da75d2a694631"}, - {file = "PyYAML-6.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:39693e1f8320ae4f43943590b49779ffb98acb81f788220ea932a6b6c51004d8"}, - {file = "pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e"}, -] - -[[package]] -name = "rich" -version = "13.9.1" -description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal" -category = "main" -optional = false -python-versions = ">=3.8.0" -files = [ - {file = "rich-13.9.1-py3-none-any.whl", hash = "sha256:b340e739f30aa58921dc477b8adaa9ecdb7cecc217be01d93730ee1bc8aa83be"}, - {file = "rich-13.9.1.tar.gz", hash = "sha256:097cffdf85db1babe30cc7deba5ab3a29e1b9885047dab24c57e9a7f8a9c1466"}, -] - -[package.dependencies] -markdown-it-py = ">=2.2.0" -pygments = ">=2.13.0,<3.0.0" -typing-extensions = {version = ">=4.0.0,<5.0", markers = "python_version < \"3.11\""} - -[package.extras] -jupyter = ["ipywidgets (>=7.5.1,<9)"] - -[[package]] -name = "scipy" -version = "1.14.1" -description = "Fundamental algorithms for scientific computing in Python" -category = "main" -optional = false -python-versions = ">=3.10" -files = [ - {file = "scipy-1.14.1-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:b28d2ca4add7ac16ae8bb6632a3c86e4b9e4d52d3e34267f6e1b0c1f8d87e389"}, - {file = "scipy-1.14.1-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:d0d2821003174de06b69e58cef2316a6622b60ee613121199cb2852a873f8cf3"}, - {file = "scipy-1.14.1-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:8bddf15838ba768bb5f5083c1ea012d64c9a444e16192762bd858f1e126196d0"}, - {file = "scipy-1.14.1-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:97c5dddd5932bd2a1a31c927ba5e1463a53b87ca96b5c9bdf5dfd6096e27efc3"}, - {file = "scipy-1.14.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2ff0a7e01e422c15739ecd64432743cf7aae2b03f3084288f399affcefe5222d"}, - {file = "scipy-1.14.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8e32dced201274bf96899e6491d9ba3e9a5f6b336708656466ad0522d8528f69"}, - {file = "scipy-1.14.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:8426251ad1e4ad903a4514712d2fa8fdd5382c978010d1c6f5f37ef286a713ad"}, - {file = "scipy-1.14.1-cp310-cp310-win_amd64.whl", hash = "sha256:a49f6ed96f83966f576b33a44257d869756df6cf1ef4934f59dd58b25e0327e5"}, - {file = "scipy-1.14.1-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:2da0469a4ef0ecd3693761acbdc20f2fdeafb69e6819cc081308cc978153c675"}, - {file = "scipy-1.14.1-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:c0ee987efa6737242745f347835da2cc5bb9f1b42996a4d97d5c7ff7928cb6f2"}, - {file = "scipy-1.14.1-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:3a1b111fac6baec1c1d92f27e76511c9e7218f1695d61b59e05e0fe04dc59617"}, - {file = "scipy-1.14.1-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:8475230e55549ab3f207bff11ebfc91c805dc3463ef62eda3ccf593254524ce8"}, - {file = "scipy-1.14.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:278266012eb69f4a720827bdd2dc54b2271c97d84255b2faaa8f161a158c3b37"}, - {file = "scipy-1.14.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fef8c87f8abfb884dac04e97824b61299880c43f4ce675dd2cbeadd3c9b466d2"}, - {file = "scipy-1.14.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b05d43735bb2f07d689f56f7b474788a13ed8adc484a85aa65c0fd931cf9ccd2"}, - {file = "scipy-1.14.1-cp311-cp311-win_amd64.whl", hash = "sha256:716e389b694c4bb564b4fc0c51bc84d381735e0d39d3f26ec1af2556ec6aad94"}, - {file = "scipy-1.14.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:631f07b3734d34aced009aaf6fedfd0eb3498a97e581c3b1e5f14a04164a456d"}, - {file = "scipy-1.14.1-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:af29a935803cc707ab2ed7791c44288a682f9c8107bc00f0eccc4f92c08d6e07"}, - {file = "scipy-1.14.1-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:2843f2d527d9eebec9a43e6b406fb7266f3af25a751aa91d62ff416f54170bc5"}, - {file = "scipy-1.14.1-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:eb58ca0abd96911932f688528977858681a59d61a7ce908ffd355957f7025cfc"}, - {file = "scipy-1.14.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:30ac8812c1d2aab7131a79ba62933a2a76f582d5dbbc695192453dae67ad6310"}, - {file = "scipy-1.14.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f9ea80f2e65bdaa0b7627fb00cbeb2daf163caa015e59b7516395fe3bd1e066"}, - {file = "scipy-1.14.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:edaf02b82cd7639db00dbff629995ef185c8df4c3ffa71a5562a595765a06ce1"}, - {file = "scipy-1.14.1-cp312-cp312-win_amd64.whl", hash = "sha256:2ff38e22128e6c03ff73b6bb0f85f897d2362f8c052e3b8ad00532198fbdae3f"}, - {file = "scipy-1.14.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:1729560c906963fc8389f6aac023739ff3983e727b1a4d87696b7bf108316a79"}, - {file = "scipy-1.14.1-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:4079b90df244709e675cdc8b93bfd8a395d59af40b72e339c2287c91860deb8e"}, - {file = "scipy-1.14.1-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:e0cf28db0f24a38b2a0ca33a85a54852586e43cf6fd876365c86e0657cfe7d73"}, - {file = "scipy-1.14.1-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:0c2f95de3b04e26f5f3ad5bb05e74ba7f68b837133a4492414b3afd79dfe540e"}, - {file = "scipy-1.14.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b99722ea48b7ea25e8e015e8341ae74624f72e5f21fc2abd45f3a93266de4c5d"}, - {file = "scipy-1.14.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5149e3fd2d686e42144a093b206aef01932a0059c2a33ddfa67f5f035bdfe13e"}, - {file = "scipy-1.14.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e4f5a7c49323533f9103d4dacf4e4f07078f360743dec7f7596949149efeec06"}, - {file = "scipy-1.14.1-cp313-cp313-win_amd64.whl", hash = "sha256:baff393942b550823bfce952bb62270ee17504d02a1801d7fd0719534dfb9c84"}, - {file = "scipy-1.14.1.tar.gz", hash = "sha256:5a275584e726026a5699459aa72f828a610821006228e841b94275c4a7c08417"}, -] - -[package.dependencies] -numpy = ">=1.23.5,<2.3" - -[package.extras] -dev = ["cython-lint (>=0.12.2)", "doit (>=0.36.0)", "mypy (==1.10.0)", "pycodestyle", "pydevtool", "rich-click", "ruff (>=0.0.292)", "types-psutil", "typing_extensions"] -doc = ["jupyterlite-pyodide-kernel", "jupyterlite-sphinx (>=0.13.1)", "jupytext", "matplotlib (>=3.5)", "myst-nb", "numpydoc", "pooch", "pydata-sphinx-theme (>=0.15.2)", "sphinx (>=5.0.0,<=7.3.7)", "sphinx-design (>=0.4.0)"] -test = ["Cython", "array-api-strict (>=2.0)", "asv", "gmpy2", "hypothesis (>=6.30)", "meson", "mpmath", "ninja", "pooch", "pytest", "pytest-cov", "pytest-timeout", "pytest-xdist", "scikit-umfpack", "threadpoolctl"] - -[[package]] -name = "scriptconfig" -version = "0.8.0" -description = "Easy dict-based script configuration with CLI support" -category = "main" -optional = false -python-versions = ">=3.6" -files = [ - {file = "scriptconfig-0.8.0-py3-none-any.whl", hash = "sha256:c327e5cc4f136d4c87d08b97b4ae7ac20cbcb4f9120fd029442066758c3d2d36"}, - {file = "scriptconfig-0.8.0.tar.gz", hash = "sha256:d641365029d784cee577cfb8c4a213b83f05e9c2ea17329af0f707941326c975"}, -] - -[package.dependencies] -PyYAML = [ - {version = ">=6.0", markers = "python_version >= \"3.10\" and python_version < \"3.12\""}, - {version = ">=6.0.1", markers = "python_version < \"4.0\" and python_version >= \"3.12\""}, -] -ubelt = ">=1.3.6" - -[package.extras] -all = ["PyYAML (>=5.4.1)", "PyYAML (>=5.4.1)", "PyYAML (>=5.4.1)", "PyYAML (>=5.4.1)", "PyYAML (>=6.0)", "PyYAML (>=6.0)", "PyYAML (>=6.0.1)", "argcomplete (>=3.0.5)", "coverage (>=4.3.4)", "coverage (>=4.5)", "coverage (>=5.3.1)", "coverage (>=5.3.1)", "coverage (>=5.3.1)", "coverage (>=6.1.1)", "coverage (>=6.1.1)", "coverage (>=6.1.1)", "coverage (>=6.1.1)", "numpy (>=1.11.1)", "numpy (>=1.11.1)", "numpy (>=1.11.1)", "numpy (>=1.12.0)", "numpy (>=1.14.5)", "numpy (>=1.19.2)", "numpy (>=1.19.3)", "numpy (>=1.21.6)", "numpy (>=1.23.2)", "numpy (>=1.26.0)", "omegaconf (>=2.2.2)", "pytest (>=4.6.0)", "pytest (>=4.6.0)", "pytest (>=4.6.0,<=4.6.11)", "pytest (>=4.6.0,<=4.6.11)", "pytest (>=4.6.0,<=6.1.2)", "pytest (>=6.2.5)", "pytest-cov (>=2.8.1)", "pytest-cov (>=2.8.1)", "pytest-cov (>=2.9.0)", "pytest-cov (>=3.0.0)", "rich-argparse (>=1.1.0)", "ubelt (>=1.3.6)", "xdoctest (>=1.1.5)"] -all-strict = ["PyYAML (==5.4.1)", "PyYAML (==5.4.1)", "PyYAML (==5.4.1)", "PyYAML (==5.4.1)", "PyYAML (==6.0)", "PyYAML (==6.0)", "PyYAML (==6.0.1)", "argcomplete (==3.0.5)", "coverage (==4.3.4)", "coverage (==4.5)", "coverage (==5.3.1)", "coverage (==5.3.1)", "coverage (==5.3.1)", "coverage (==6.1.1)", "coverage (==6.1.1)", "coverage (==6.1.1)", "coverage (==6.1.1)", "numpy (==1.11.1)", "numpy (==1.11.1)", "numpy (==1.11.1)", "numpy (==1.12.0)", "numpy (==1.14.5)", "numpy (==1.19.2)", "numpy (==1.19.3)", "numpy (==1.21.6)", "numpy (==1.23.2)", "numpy (==1.26.0)", "omegaconf (==2.2.2)", "pytest (==4.6.0)", "pytest (==4.6.0)", "pytest (==4.6.0)", "pytest (==4.6.0)", "pytest (==4.6.0)", "pytest (==6.2.5)", "pytest-cov (==2.8.1)", "pytest-cov (==2.8.1)", "pytest-cov (==2.9.0)", "pytest-cov (==3.0.0)", "rich-argparse (==1.1.0)", "ubelt (==1.3.6)", "xdoctest (==1.1.5)"] -optional = ["argcomplete (>=3.0.5)", "numpy (>=1.11.1)", "numpy (>=1.11.1)", "numpy (>=1.11.1)", "numpy (>=1.12.0)", "numpy (>=1.14.5)", "numpy (>=1.19.2)", "numpy (>=1.19.3)", "numpy (>=1.21.6)", "numpy (>=1.23.2)", "numpy (>=1.26.0)", "omegaconf (>=2.2.2)", "rich-argparse (>=1.1.0)"] -optional-strict = ["argcomplete (==3.0.5)", "numpy (==1.11.1)", "numpy (==1.11.1)", "numpy (==1.11.1)", "numpy (==1.12.0)", "numpy (==1.14.5)", "numpy (==1.19.2)", "numpy (==1.19.3)", "numpy (==1.21.6)", "numpy (==1.23.2)", "numpy (==1.26.0)", "omegaconf (==2.2.2)", "rich-argparse (==1.1.0)"] -runtime-strict = ["PyYAML (==5.4.1)", "PyYAML (==5.4.1)", "PyYAML (==5.4.1)", "PyYAML (==5.4.1)", "PyYAML (==6.0)", "PyYAML (==6.0)", "PyYAML (==6.0.1)", "ubelt (==1.3.6)"] -tests = ["coverage (>=4.3.4)", "coverage (>=4.5)", "coverage (>=5.3.1)", "coverage (>=5.3.1)", "coverage (>=5.3.1)", "coverage (>=6.1.1)", "coverage (>=6.1.1)", "coverage (>=6.1.1)", "coverage (>=6.1.1)", "pytest (>=4.6.0)", "pytest (>=4.6.0)", "pytest (>=4.6.0,<=4.6.11)", "pytest (>=4.6.0,<=4.6.11)", "pytest (>=4.6.0,<=6.1.2)", "pytest (>=6.2.5)", "pytest-cov (>=2.8.1)", "pytest-cov (>=2.8.1)", "pytest-cov (>=2.9.0)", "pytest-cov (>=3.0.0)", "xdoctest (>=1.1.5)"] -tests-strict = ["coverage (==4.3.4)", "coverage (==4.5)", "coverage (==5.3.1)", "coverage (==5.3.1)", "coverage (==5.3.1)", "coverage (==6.1.1)", "coverage (==6.1.1)", "coverage (==6.1.1)", "coverage (==6.1.1)", "pytest (==4.6.0)", "pytest (==4.6.0)", "pytest (==4.6.0)", "pytest (==4.6.0)", "pytest (==4.6.0)", "pytest (==6.2.5)", "pytest-cov (==2.8.1)", "pytest-cov (==2.8.1)", "pytest-cov (==2.9.0)", "pytest-cov (==3.0.0)", "xdoctest (==1.1.5)"] - -[[package]] -name = "setuptools" -version = "75.1.0" -description = "Easily download, build, install, upgrade, and uninstall Python packages" -category = "main" -optional = false -python-versions = ">=3.8" -files = [ - {file = "setuptools-75.1.0-py3-none-any.whl", hash = "sha256:35ab7fd3bcd95e6b7fd704e4a1539513edad446c097797f2985e0e4b960772f2"}, - {file = "setuptools-75.1.0.tar.gz", hash = "sha256:d59a21b17a275fb872a9c3dae73963160ae079f1049ed956880cd7c09b120538"}, -] - -[package.extras] -check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)", "ruff (>=0.5.2)"] -core = ["importlib-metadata (>=6)", "importlib-resources (>=5.10.2)", "jaraco.collections", "jaraco.functools", "jaraco.text (>=3.7)", "more-itertools", "more-itertools (>=8.8)", "packaging", "packaging (>=24)", "platformdirs (>=2.6.2)", "tomli (>=2.0.1)", "wheel (>=0.43.0)"] -cover = ["pytest-cov"] -doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "pyproject-hooks (!=1.1)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier", "towncrier (<24.7)"] -enabler = ["pytest-enabler (>=2.2)"] -test = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "ini2toml[lite] (>=0.14)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "jaraco.test", "packaging (>=23.2)", "pip (>=19.1)", "pyproject-hooks (!=1.1)", "pytest (>=6,!=8.1.*)", "pytest-home (>=0.5)", "pytest-perf", "pytest-subprocess", "pytest-timeout", "pytest-xdist (>=3)", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel (>=0.44.0)"] -type = ["importlib-metadata (>=7.0.2)", "jaraco.develop (>=7.21)", "mypy (>=1.11.0,<1.12.0)", "pytest-mypy"] - -[[package]] -name = "shapely" -version = "2.0.6" -description = "Manipulation and analysis of geometric objects" -category = "main" -optional = false -python-versions = ">=3.7" -files = [ - {file = "shapely-2.0.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:29a34e068da2d321e926b5073539fd2a1d4429a2c656bd63f0bd4c8f5b236d0b"}, - {file = "shapely-2.0.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e1c84c3f53144febf6af909d6b581bc05e8785d57e27f35ebaa5c1ab9baba13b"}, - {file = "shapely-2.0.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2ad2fae12dca8d2b727fa12b007e46fbc522148a584f5d6546c539f3464dccde"}, - {file = "shapely-2.0.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b3304883bd82d44be1b27a9d17f1167fda8c7f5a02a897958d86c59ec69b705e"}, - {file = "shapely-2.0.6-cp310-cp310-win32.whl", hash = "sha256:3ec3a0eab496b5e04633a39fa3d5eb5454628228201fb24903d38174ee34565e"}, - {file = "shapely-2.0.6-cp310-cp310-win_amd64.whl", hash = "sha256:28f87cdf5308a514763a5c38de295544cb27429cfa655d50ed8431a4796090c4"}, - {file = "shapely-2.0.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5aeb0f51a9db176da9a30cb2f4329b6fbd1e26d359012bb0ac3d3c7781667a9e"}, - {file = "shapely-2.0.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9a7a78b0d51257a367ee115f4d41ca4d46edbd0dd280f697a8092dd3989867b2"}, - {file = "shapely-2.0.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f32c23d2f43d54029f986479f7c1f6e09c6b3a19353a3833c2ffb226fb63a855"}, - {file = "shapely-2.0.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b3dc9fb0eb56498912025f5eb352b5126f04801ed0e8bdbd867d21bdbfd7cbd0"}, - {file = "shapely-2.0.6-cp311-cp311-win32.whl", hash = "sha256:d93b7e0e71c9f095e09454bf18dad5ea716fb6ced5df3cb044564a00723f339d"}, - {file = "shapely-2.0.6-cp311-cp311-win_amd64.whl", hash = "sha256:c02eb6bf4cfb9fe6568502e85bb2647921ee49171bcd2d4116c7b3109724ef9b"}, - {file = "shapely-2.0.6-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:cec9193519940e9d1b86a3b4f5af9eb6910197d24af02f247afbfb47bcb3fab0"}, - {file = "shapely-2.0.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:83b94a44ab04a90e88be69e7ddcc6f332da7c0a0ebb1156e1c4f568bbec983c3"}, - {file = "shapely-2.0.6-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:537c4b2716d22c92036d00b34aac9d3775e3691f80c7aa517c2c290351f42cd8"}, - {file = "shapely-2.0.6-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:98fea108334be345c283ce74bf064fa00cfdd718048a8af7343c59eb40f59726"}, - {file = "shapely-2.0.6-cp312-cp312-win32.whl", hash = "sha256:42fd4cd4834747e4990227e4cbafb02242c0cffe9ce7ef9971f53ac52d80d55f"}, - {file = "shapely-2.0.6-cp312-cp312-win_amd64.whl", hash = "sha256:665990c84aece05efb68a21b3523a6b2057e84a1afbef426ad287f0796ef8a48"}, - {file = "shapely-2.0.6-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:42805ef90783ce689a4dde2b6b2f261e2c52609226a0438d882e3ced40bb3013"}, - {file = "shapely-2.0.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:6d2cb146191a47bd0cee8ff5f90b47547b82b6345c0d02dd8b25b88b68af62d7"}, - {file = "shapely-2.0.6-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e3fdef0a1794a8fe70dc1f514440aa34426cc0ae98d9a1027fb299d45741c381"}, - {file = "shapely-2.0.6-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2c665a0301c645615a107ff7f52adafa2153beab51daf34587170d85e8ba6805"}, - {file = "shapely-2.0.6-cp313-cp313-win32.whl", hash = "sha256:0334bd51828f68cd54b87d80b3e7cee93f249d82ae55a0faf3ea21c9be7b323a"}, - {file = "shapely-2.0.6-cp313-cp313-win_amd64.whl", hash = "sha256:d37d070da9e0e0f0a530a621e17c0b8c3c9d04105655132a87cfff8bd77cc4c2"}, - {file = "shapely-2.0.6-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:fa7468e4f5b92049c0f36d63c3e309f85f2775752e076378e36c6387245c5462"}, - {file = "shapely-2.0.6-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ed5867e598a9e8ac3291da6cc9baa62ca25706eea186117034e8ec0ea4355653"}, - {file = "shapely-2.0.6-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:81d9dfe155f371f78c8d895a7b7f323bb241fb148d848a2bf2244f79213123fe"}, - {file = "shapely-2.0.6-cp37-cp37m-win32.whl", hash = "sha256:fbb7bf02a7542dba55129062570211cfb0defa05386409b3e306c39612e7fbcc"}, - {file = "shapely-2.0.6-cp37-cp37m-win_amd64.whl", hash = "sha256:837d395fac58aa01aa544495b97940995211e3e25f9aaf87bc3ba5b3a8cd1ac7"}, - {file = "shapely-2.0.6-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:c6d88ade96bf02f6bfd667ddd3626913098e243e419a0325ebef2bbd481d1eb6"}, - {file = "shapely-2.0.6-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:8b3b818c4407eaa0b4cb376fd2305e20ff6df757bf1356651589eadc14aab41b"}, - {file = "shapely-2.0.6-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1bbc783529a21f2bd50c79cef90761f72d41c45622b3e57acf78d984c50a5d13"}, - {file = "shapely-2.0.6-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2423f6c0903ebe5df6d32e0066b3d94029aab18425ad4b07bf98c3972a6e25a1"}, - {file = "shapely-2.0.6-cp38-cp38-win32.whl", hash = "sha256:2de00c3bfa80d6750832bde1d9487e302a6dd21d90cb2f210515cefdb616e5f5"}, - {file = "shapely-2.0.6-cp38-cp38-win_amd64.whl", hash = "sha256:3a82d58a1134d5e975f19268710e53bddd9c473743356c90d97ce04b73e101ee"}, - {file = "shapely-2.0.6-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:392f66f458a0a2c706254f473290418236e52aa4c9b476a072539d63a2460595"}, - {file = "shapely-2.0.6-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:eba5bae271d523c938274c61658ebc34de6c4b33fdf43ef7e938b5776388c1be"}, - {file = "shapely-2.0.6-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7060566bc4888b0c8ed14b5d57df8a0ead5c28f9b69fb6bed4476df31c51b0af"}, - {file = "shapely-2.0.6-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b02154b3e9d076a29a8513dffcb80f047a5ea63c897c0cd3d3679f29363cf7e5"}, - {file = "shapely-2.0.6-cp39-cp39-win32.whl", hash = "sha256:44246d30124a4f1a638a7d5419149959532b99dfa25b54393512e6acc9c211ac"}, - {file = "shapely-2.0.6-cp39-cp39-win_amd64.whl", hash = "sha256:2b542d7f1dbb89192d3512c52b679c822ba916f93479fa5d4fc2fe4fa0b3c9e8"}, - {file = "shapely-2.0.6.tar.gz", hash = "sha256:997f6159b1484059ec239cacaa53467fd8b5564dabe186cd84ac2944663b0bf6"}, -] - -[package.dependencies] -numpy = ">=1.14,<3" - -[package.extras] -docs = ["matplotlib", "numpydoc (>=1.1.0,<1.2.0)", "sphinx", "sphinx-book-theme", "sphinx-remove-toctrees"] -test = ["pytest", "pytest-cov"] - -[[package]] -name = "simplekml" -version = "1.3.6" -description = "A Simple KML creator" -category = "main" -optional = false -python-versions = "*" -files = [ - {file = "simplekml-1.3.6.tar.gz", hash = "sha256:cda687be2754395fcab664e908ebf589facd41e8436d233d2be37a69efb1c536"}, -] - -[[package]] -name = "six" -version = "1.16.0" -description = "Python 2 and 3 compatibility utilities" -category = "main" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" -files = [ - {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, - {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, -] - -[[package]] -name = "stack-data" -version = "0.6.3" -description = "Extract data from python stack frames and tracebacks for informative displays" -category = "main" -optional = false -python-versions = "*" -files = [ - {file = "stack_data-0.6.3-py3-none-any.whl", hash = "sha256:d5558e0c25a4cb0853cddad3d77da9891a08cb85dd9f9f91b9f8cd66e511e695"}, - {file = "stack_data-0.6.3.tar.gz", hash = "sha256:836a778de4fec4dcd1dcd89ed8abff8a221f58308462e1c4aa2a3cf30148f0b9"}, -] - -[package.dependencies] -asttokens = ">=2.1.0" -executing = ">=1.2.0" -pure-eval = "*" - -[package.extras] -tests = ["cython", "littleutils", "pygments", "pytest", "typeguard"] - -[[package]] -name = "tomli" -version = "2.0.2" -description = "A lil' TOML parser" -category = "main" -optional = false -python-versions = ">=3.8" -files = [ - {file = "tomli-2.0.2-py3-none-any.whl", hash = "sha256:2ebe24485c53d303f690b0ec092806a085f07af5a5aa1464f3931eec36caaa38"}, - {file = "tomli-2.0.2.tar.gz", hash = "sha256:d46d457a85337051c36524bc5349dd91b1877838e2979ac5ced3e710ed8a60ed"}, -] - -[[package]] -name = "traitlets" -version = "5.14.3" -description = "Traitlets Python configuration system" -category = "main" -optional = false -python-versions = ">=3.8" -files = [ - {file = "traitlets-5.14.3-py3-none-any.whl", hash = "sha256:b74e89e397b1ed28cc831db7aea759ba6640cb3de13090ca145426688ff1ac4f"}, - {file = "traitlets-5.14.3.tar.gz", hash = "sha256:9ed0579d3502c94b4b3732ac120375cda96f923114522847de4b3bb98b96b6b7"}, -] - -[package.extras] -docs = ["myst-parser", "pydata-sphinx-theme", "sphinx"] -test = ["argcomplete (>=3.0.3)", "mypy (>=1.7.0)", "pre-commit", "pytest (>=7.0,<8.2)", "pytest-mock", "pytest-mypy-testing"] - -[[package]] -name = "transformations" -version = "2024.5.24" -description = "Homogeneous Transformation Matrices and Quaternions" -category = "main" -optional = false -python-versions = ">=3.9" -files = [ - {file = "transformations-2024.5.24-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b538ec7fc815c4ab6f427e339eb1a4eddd46860fff306f8f7c6eb80167d08d28"}, - {file = "transformations-2024.5.24-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0c3c6b07e1d0d9714f3ccd163785845ff780df26ab094ed125b11d0922c4fee7"}, - {file = "transformations-2024.5.24-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cc36351fed3d7bc2d413bab358828870f02a41d71723033032d08e2e75361892"}, - {file = "transformations-2024.5.24-cp310-cp310-win32.whl", hash = "sha256:5ddcec2e35a05a3f81a318c4d7184a4c347b85030cdbc36fdad92c7eb2ba70d4"}, - {file = "transformations-2024.5.24-cp310-cp310-win_amd64.whl", hash = "sha256:c599f0140f18f590c50992f5011593ce08e4b893ff26fde4bf7bf5e0db1d9c60"}, - {file = "transformations-2024.5.24-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1290cdcb65067809cd8dd4b3c3ab552fb6b50e99c015c1678618de2b67913b72"}, - {file = "transformations-2024.5.24-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:575d0b5d8c2c92931da46fc55568f17f7b306334ba25fa6091ac5708808bfbfc"}, - {file = "transformations-2024.5.24-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fba5109b455836829ce6476ba53c16f6e72187340084d004dcfac9bf11a2bfd7"}, - {file = "transformations-2024.5.24-cp311-cp311-win32.whl", hash = "sha256:c76e772b77d4665440dac82b36485565e0e0d4ef3679b48ef4449b73935e44fb"}, - {file = "transformations-2024.5.24-cp311-cp311-win_amd64.whl", hash = "sha256:22fd2283b4aa51609ce3d6929add2fef656f049c5cfa291cc6abfdae2a329f7f"}, - {file = "transformations-2024.5.24-cp311-cp311-win_arm64.whl", hash = "sha256:be69c3055aa341db7bd231d979ebaae1e64417246a6792c1d03631193edde442"}, - {file = "transformations-2024.5.24-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:cf0a5f141dcf84a7b5efb27747c2b852d1f9cc8d621d3bdc17fb2f9ac37eaee3"}, - {file = "transformations-2024.5.24-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a655966a893ade762f12946c02e3ffa5cd826cf0866e9587e46f812d494ad56e"}, - {file = "transformations-2024.5.24-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2f87541e8bb87337b8bc1839188d07ad922124f23ce1eca1ce4c6f5bcf5f1e4f"}, - {file = "transformations-2024.5.24-cp312-cp312-win32.whl", hash = "sha256:f713ac891a46abfd98158c8a72166a1f92ebaba1644dc8aa59b5357391d6d9e0"}, - {file = "transformations-2024.5.24-cp312-cp312-win_amd64.whl", hash = "sha256:d81ed1df5dbdd204cd447728cc5b44d4bf1ccd4af5401ee61032477f5de5788b"}, - {file = "transformations-2024.5.24-cp312-cp312-win_arm64.whl", hash = "sha256:bbb74d4701be2fc7852c80493e2b92cd52485d4712ce8bfc03c92fb7bdd365c5"}, - {file = "transformations-2024.5.24-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d70a175f7baff6cc70236f74d51c5adb14a3cc3d89fc2e108c1f086f9a834e88"}, - {file = "transformations-2024.5.24-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:29f4ebee83c2fb6857f7dbb15917e39bc6bed1f309179452a0dfeda37e2d4d0d"}, - {file = "transformations-2024.5.24-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:00b20a57cfe4b5f5fadf957bff68c40a86437f85e75b2ed655b87beb45d62940"}, - {file = "transformations-2024.5.24-cp39-cp39-win32.whl", hash = "sha256:873a7d09a428ccf0156949b1541eae93ee954771c7b431e7d15a79ab2c32d5e4"}, - {file = "transformations-2024.5.24-cp39-cp39-win_amd64.whl", hash = "sha256:dda37a7683acb10068c491b6d33bdbdbfb1a1ea4a3291639b23ee4a0af42138c"}, - {file = "transformations-2024.5.24.tar.gz", hash = "sha256:960328ce2f5e1dad8025b1b82c8588afbc57644c609899c5b9508af965cd7bc0"}, -] - -[package.dependencies] -numpy = "*" - -[[package]] -name = "typing-extensions" -version = "4.12.2" -description = "Backported and Experimental Type Hints for Python 3.8+" -category = "main" -optional = false -python-versions = ">=3.8" -files = [ - {file = "typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"}, - {file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"}, -] - -[[package]] -name = "ubelt" -version = "1.3.6" -description = "A Python utility belt containing simple tools, a stdlib like feel, and extra batteries" -category = "main" -optional = false -python-versions = ">=3.6" -files = [ - {file = "ubelt-1.3.6-py3-none-any.whl", hash = "sha256:2a38f260e7f3c25d3618f653d5c900230dc5af56bf0bc1ff85cfdbe9d0c88f0f"}, - {file = "ubelt-1.3.6.tar.gz", hash = "sha256:327a516a1fc95595096727ae3ae879379bc56fc11fb945857b971ef85a74f698"}, -] - -[package.extras] -all = ["Pygments (>=2.2.0)", "colorama (>=0.4.3)", "coverage (>=4.3.4)", "coverage (>=4.5)", "coverage (>=5.3.1)", "coverage (>=5.3.1)", "coverage (>=5.3.1)", "coverage (>=6.1.1)", "coverage (>=6.1.1)", "coverage (>=6.1.1)", "coverage (>=6.1.1)", "coverage (>=7.3.0)", "jaraco.windows (>=3.9.1)", "numpy (>=1.12.0,<2.0.0)", "numpy (>=1.14.5,<2.0.0)", "numpy (>=1.19.2)", "numpy (>=1.19.3)", "numpy (>=1.21.1)", "numpy (>=1.23.2)", "numpy (>=1.26.0)", "packaging (>=21.0)", "pydantic (<2.0)", "pytest (>=4.6.0)", "pytest (>=4.6.0)", "pytest (>=4.6.0,<=4.6.11)", "pytest (>=4.6.0,<=4.6.11)", "pytest (>=4.6.0,<=6.1.2)", "pytest (>=6.2.5)", "pytest (>=8.1.1)", "pytest (>=8.1.1)", "pytest (>=8.1.1)", "pytest-cov (>=2.8.1)", "pytest-cov (>=2.8.1)", "pytest-cov (>=2.9.0)", "pytest-cov (>=3.0.0)", "pytest-timeout (>=1.4.2)", "pytest-timeout (>=2.3.1)", "python-dateutil (>=2.8.1)", "requests (>=2.25.1)", "xdoctest (>=1.1.3)", "xxhash (>=1.3.0)", "xxhash (>=1.3.0)", "xxhash (>=1.4.3)", "xxhash (>=2.0.2)", "xxhash (>=3.0.0)", "xxhash (>=3.2.0)", "xxhash (>=3.4.1)"] -all-strict = ["Pygments (==2.2.0)", "colorama (==0.4.3)", "coverage (==4.3.4)", "coverage (==4.5)", "coverage (==5.3.1)", "coverage (==5.3.1)", "coverage (==5.3.1)", "coverage (==6.1.1)", "coverage (==6.1.1)", "coverage (==6.1.1)", "coverage (==6.1.1)", "coverage (==7.3.0)", "jaraco.windows (==3.9.1)", "numpy (==1.12.0)", "numpy (==1.14.5)", "numpy (==1.19.2)", "numpy (==1.19.3)", "numpy (==1.21.1)", "numpy (==1.23.2)", "numpy (==1.26.0)", "packaging (==21.0)", "pydantic (<2.0)", "pytest (==4.6.0)", "pytest (==4.6.0)", "pytest (==4.6.0)", "pytest (==4.6.0)", "pytest (==4.6.0)", "pytest (==6.2.5)", "pytest (==8.1.1)", "pytest (==8.1.1)", "pytest (==8.1.1)", "pytest-cov (==2.8.1)", "pytest-cov (==2.8.1)", "pytest-cov (==2.9.0)", "pytest-cov (==3.0.0)", "pytest-timeout (==1.4.2)", "pytest-timeout (==2.3.1)", "python-dateutil (==2.8.1)", "requests (==2.25.1)", "xdoctest (==1.1.3)", "xxhash (==1.3.0)", "xxhash (==1.3.0)", "xxhash (==1.4.3)", "xxhash (==2.0.2)", "xxhash (==3.0.0)", "xxhash (==3.2.0)", "xxhash (==3.4.1)"] -docs = ["Pygments (>=2.9.0)", "myst-parser (>=0.16.1)", "sphinx (>=4.3.2)", "sphinx-autoapi (>=1.8.4)", "sphinx-autobuild (>=2021.3.14)", "sphinx-reredirects (>=0.0.1)", "sphinx-rtd-theme (>=1.0.0)", "sphinxcontrib-napoleon (>=0.7)"] -docs-strict = ["Pygments (==2.9.0)", "myst-parser (==0.16.1)", "sphinx (==4.3.2)", "sphinx-autoapi (==1.8.4)", "sphinx-autobuild (==2021.3.14)", "sphinx-reredirects (==0.0.1)", "sphinx-rtd-theme (==1.0.0)", "sphinxcontrib-napoleon (==0.7)"] -optional = ["Pygments (>=2.2.0)", "colorama (>=0.4.3)", "jaraco.windows (>=3.9.1)", "numpy (>=1.12.0,<2.0.0)", "numpy (>=1.14.5,<2.0.0)", "numpy (>=1.19.2)", "numpy (>=1.19.3)", "numpy (>=1.21.1)", "numpy (>=1.23.2)", "numpy (>=1.26.0)", "packaging (>=21.0)", "pydantic (<2.0)", "python-dateutil (>=2.8.1)", "xxhash (>=1.3.0)", "xxhash (>=1.3.0)", "xxhash (>=1.4.3)", "xxhash (>=2.0.2)", "xxhash (>=3.0.0)", "xxhash (>=3.2.0)", "xxhash (>=3.4.1)"] -optional-strict = ["Pygments (==2.2.0)", "colorama (==0.4.3)", "jaraco.windows (==3.9.1)", "numpy (==1.12.0)", "numpy (==1.14.5)", "numpy (==1.19.2)", "numpy (==1.19.3)", "numpy (==1.21.1)", "numpy (==1.23.2)", "numpy (==1.26.0)", "packaging (==21.0)", "pydantic (<2.0)", "python-dateutil (==2.8.1)", "xxhash (==1.3.0)", "xxhash (==1.3.0)", "xxhash (==1.4.3)", "xxhash (==2.0.2)", "xxhash (==3.0.0)", "xxhash (==3.2.0)", "xxhash (==3.4.1)"] -tests = ["coverage (>=4.3.4)", "coverage (>=4.5)", "coverage (>=5.3.1)", "coverage (>=5.3.1)", "coverage (>=5.3.1)", "coverage (>=6.1.1)", "coverage (>=6.1.1)", "coverage (>=6.1.1)", "coverage (>=6.1.1)", "coverage (>=7.3.0)", "pytest (>=4.6.0)", "pytest (>=4.6.0)", "pytest (>=4.6.0,<=4.6.11)", "pytest (>=4.6.0,<=4.6.11)", "pytest (>=4.6.0,<=6.1.2)", "pytest (>=6.2.5)", "pytest (>=8.1.1)", "pytest (>=8.1.1)", "pytest (>=8.1.1)", "pytest-cov (>=2.8.1)", "pytest-cov (>=2.8.1)", "pytest-cov (>=2.9.0)", "pytest-cov (>=3.0.0)", "pytest-timeout (>=1.4.2)", "pytest-timeout (>=2.3.1)", "requests (>=2.25.1)", "xdoctest (>=1.1.3)"] -tests-strict = ["coverage (==4.3.4)", "coverage (==4.5)", "coverage (==5.3.1)", "coverage (==5.3.1)", "coverage (==5.3.1)", "coverage (==6.1.1)", "coverage (==6.1.1)", "coverage (==6.1.1)", "coverage (==6.1.1)", "coverage (==7.3.0)", "pytest (==4.6.0)", "pytest (==4.6.0)", "pytest (==4.6.0)", "pytest (==4.6.0)", "pytest (==4.6.0)", "pytest (==6.2.5)", "pytest (==8.1.1)", "pytest (==8.1.1)", "pytest (==8.1.1)", "pytest-cov (==2.8.1)", "pytest-cov (==2.8.1)", "pytest-cov (==2.9.0)", "pytest-cov (==3.0.0)", "pytest-timeout (==1.4.2)", "pytest-timeout (==2.3.1)", "requests (==2.25.1)", "xdoctest (==1.1.3)"] -types = ["autoflake (>=1.4)", "mypy", "yapf (>=0.32.0)"] -types-strict = ["autoflake (==1.4)", "mypy", "yapf (==0.32.0)"] - -[[package]] -name = "wcwidth" -version = "0.2.13" -description = "Measures the displayed width of unicode strings in a terminal" -category = "main" -optional = false -python-versions = "*" -files = [ - {file = "wcwidth-0.2.13-py2.py3-none-any.whl", hash = "sha256:3da69048e4540d84af32131829ff948f1e022c1c6bdb8d6102117aac784f6859"}, - {file = "wcwidth-0.2.13.tar.gz", hash = "sha256:72ea0c06399eb286d978fdedb6923a9eb47e1c486ce63e9b4e64fc18303972b5"}, -] - -[[package]] -name = "zope-interface" -version = "7.0.3" -description = "Interfaces for Python" -category = "main" -optional = false -python-versions = ">=3.8" -files = [ - {file = "zope.interface-7.0.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9b9369671a20b8d039b8e5a1a33abd12e089e319a3383b4cc0bf5c67bd05fe7b"}, - {file = "zope.interface-7.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:db6237e8fa91ea4f34d7e2d16d74741187e9105a63bbb5686c61fea04cdbacca"}, - {file = "zope.interface-7.0.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:53d678bb1c3b784edbfb0adeebfeea6bf479f54da082854406a8f295d36f8386"}, - {file = "zope.interface-7.0.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3aa8fcbb0d3c2be1bfd013a0f0acd636f6ed570c287743ae2bbd467ee967154d"}, - {file = "zope.interface-7.0.3-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6195c3c03fef9f87c0dbee0b3b6451df6e056322463cf35bca9a088e564a3c58"}, - {file = "zope.interface-7.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:11fa1382c3efb34abf16becff8cb214b0b2e3144057c90611621f2d186b7e1b7"}, - {file = "zope.interface-7.0.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:af94e429f9d57b36e71ef4e6865182090648aada0cb2d397ae2b3f7fc478493a"}, - {file = "zope.interface-7.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6dd647fcd765030638577fe6984284e0ebba1a1008244c8a38824be096e37fe3"}, - {file = "zope.interface-7.0.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1bee1b722077d08721005e8da493ef3adf0b7908e0cd85cc7dc836ac117d6f32"}, - {file = "zope.interface-7.0.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2545d6d7aac425d528cd9bf0d9e55fcd47ab7fd15f41a64b1c4bf4c6b24946dc"}, - {file = "zope.interface-7.0.3-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6d04b11ea47c9c369d66340dbe51e9031df2a0de97d68f442305ed7625ad6493"}, - {file = "zope.interface-7.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:064ade95cb54c840647205987c7b557f75d2b2f7d1a84bfab4cf81822ef6e7d1"}, - {file = "zope.interface-7.0.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:3fcdc76d0cde1c09c37b7c6b0f8beba2d857d8417b055d4f47df9c34ec518bdd"}, - {file = "zope.interface-7.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3d4b91821305c8d8f6e6207639abcbdaf186db682e521af7855d0bea3047c8ca"}, - {file = "zope.interface-7.0.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:35062d93bc49bd9b191331c897a96155ffdad10744ab812485b6bad5b588d7e4"}, - {file = "zope.interface-7.0.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c96b3e6b0d4f6ddfec4e947130ec30bd2c7b19db6aa633777e46c8eecf1d6afd"}, - {file = "zope.interface-7.0.3-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e0c151a6c204f3830237c59ee4770cc346868a7a1af6925e5e38650141a7f05"}, - {file = "zope.interface-7.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:3de1d553ce72868b77a7e9d598c9bff6d3816ad2b4cc81c04f9d8914603814f3"}, - {file = "zope.interface-7.0.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ab985c566a99cc5f73bc2741d93f1ed24a2cc9da3890144d37b9582965aff996"}, - {file = "zope.interface-7.0.3-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d976fa7b5faf5396eb18ce6c132c98e05504b52b60784e3401f4ef0b2e66709b"}, - {file = "zope.interface-7.0.3-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:21a207c6b2c58def5011768140861a73f5240f4f39800625072ba84e76c9da0b"}, - {file = "zope.interface-7.0.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:382d31d1e68877061daaa6499468e9eb38eb7625d4369b1615ac08d3860fe896"}, - {file = "zope.interface-7.0.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:2c4316a30e216f51acbd9fb318aa5af2e362b716596d82cbb92f9101c8f8d2e7"}, - {file = "zope.interface-7.0.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:01e6e58078ad2799130c14a1d34ec89044ada0e1495329d72ee0407b9ae5100d"}, - {file = "zope.interface-7.0.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:799ef7a444aebbad5a145c3b34bff012b54453cddbde3332d47ca07225792ea4"}, - {file = "zope.interface-7.0.3-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d3b7ce6d46fb0e60897d62d1ff370790ce50a57d40a651db91a3dde74f73b738"}, - {file = "zope.interface-7.0.3-cp38-cp38-win_amd64.whl", hash = "sha256:f418c88f09c3ba159b95a9d1cfcdbe58f208443abb1f3109f4b9b12fd60b187c"}, - {file = "zope.interface-7.0.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:84f8794bd59ca7d09d8fce43ae1b571be22f52748169d01a13d3ece8394d8b5b"}, - {file = "zope.interface-7.0.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:7d92920416f31786bc1b2f34cc4fc4263a35a407425319572cbf96b51e835cd3"}, - {file = "zope.interface-7.0.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:95e5913ec718010dc0e7c215d79a9683b4990e7026828eedfda5268e74e73e11"}, - {file = "zope.interface-7.0.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1eeeb92cb7d95c45e726e3c1afe7707919370addae7ed14f614e22217a536958"}, - {file = "zope.interface-7.0.3-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ecd32f30f40bfd8511b17666895831a51b532e93fc106bfa97f366589d3e4e0e"}, - {file = "zope.interface-7.0.3-cp39-cp39-win_amd64.whl", hash = "sha256:5112c530fa8aa2108a3196b9c2f078f5738c1c37cfc716970edc0df0414acda8"}, - {file = "zope.interface-7.0.3.tar.gz", hash = "sha256:cd2690d4b08ec9eaf47a85914fe513062b20da78d10d6d789a792c0b20307fb1"}, -] - -[package.dependencies] -setuptools = "*" - -[package.extras] -docs = ["Sphinx", "repoze.sphinx.autointerface", "sphinx-rtd-theme"] -test = ["coverage (>=5.0.3)", "zope.event", "zope.testing"] -testing = ["coverage (>=5.0.3)", "zope.event", "zope.testing"] - -[metadata] -lock-version = "2.0" -python-versions = "^3.10" -content-hash = "f12fd4cb5e2ba880d79c054b211f719ad02260180998d7090af72a2cf619e730" diff --git a/pyproject.toml b/pyproject.toml index 1268c081..070edb0f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,39 +1,58 @@ -[tool.poetry] +[project] name = "kamera" version = "0.1.0" description = "KAMERA: Kitware's Image Acquisition ManagER and Archiver" -authors = ["Adam Romlein "] +authors = [ + { name = "Adam Romlein", email = "adam.romlein@kitware.com" }, +] license = "Apache-2.0" readme = "README.md" +requires-python = ">=3.10" +dependencies = [ + "numpy>=2.1.1", + "scipy>=1.14.1", + "matplotlib>=3.9.2", + "opencv-python>=4.10.0.84", + "pillow>=10.4.0", + "pyyaml>=6.0.2", + "datetime>=5.5", + "exifread>=3.0.0", + "pygeodesy>=24.9.29", + "pyshp>=2.3.1", + "simplekml>=1.3.6", + "shapely>=2.0.6", + "transformations>=2024.5.24", + "scriptconfig>=0.8.0", + "ubelt>=1.3.6", + "rich>=13.9.1", +] + +# GDAL and pycolmap (>=4.2, CUDA build) have no reliable cross-platform wheels; they +# come from conda-forge (environment.yml) and reach .venv via --system-site-packages. + +[project.scripts] +kamera-calibrate = "kamera.calibration.cli:main" -[tool.poetry.dependencies] -python = ">=3.8,<4" -numpy = {version = "^2.1.1", markers = "python_version >= '3.10'"} -scipy = {version = "^1.14.1", markers = "python_version >= '3.10'"} -matplotlib = {version = "^3.9.2", markers = "python_version >= '3.10'"} -opencv-python = {version = "^4.10.0.84", markers = "python_version >= '3.10'"} -pillow = {version = "^10.4.0", markers = "python_version >= '3.10'"} -pyyaml = {version = "^6.0.2", markers = "python_version >= '3.10'"} -datetime = {version = "^5.5", markers = "python_version >= '3.10'"} -exifread = {version = "^3.0.0", markers = "python_version >= '3.10'"} -pygeodesy = {version = "^24.9.29", markers = "python_version >= '3.10'"} -pyshp = {version = "^2.3.1", markers = "python_version >= '3.10'"} -simplekml = {version = "^1.3.6", markers = "python_version >= '3.10'"} -shapely = {version = "^2.0.6", markers = "python_version >= '3.10'"} -transformations = {version = "^2024.5.24", markers = "python_version >= '3.9'"} -scriptconfig = {version = "^0.8.0", markers = "python_version >= '3.10'"} -ubelt = {version = "^1.3.6", markers = "python_version >= '3.10'"} -rich = {version = "^13.9.1", markers = "python_version >= '3.10'"} -gdal = [ - { url = "https://github.com/girder/large_image_wheels/raw/wheelhouse/GDAL-3.10.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl#sha256=8c150cc85623d136734eb2fad91036933cbb2d6ba58a76095b82ddf53d9bf961", markers = "python_version >= '3.8' and python_version < '3.9'" }, - { url = "https://github.com/girder/large_image_wheels/raw/wheelhouse/GDAL-3.10.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl#sha256=7894fddd09d31530d764d5f5e52faafa3585b906c2e49c79fe593af8c6a34c24", markers = "python_version >= '3.9' and python_version < '3.10'" }, - { url = "https://github.com/girder/large_image_wheels/raw/wheelhouse/GDAL-3.10.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl#sha256=4a09086631d81808a97c8c7a605aa6230ca045874aa688863cf91794e94880c7", markers = "python_version >= '3.10' and python_version < '3.11'" }, - { url = "https://github.com/girder/large_image_wheels/raw/wheelhouse/GDAL-3.10.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl#sha256=aee8c49c3528b8613ad3fe14a9d0066ad6990e143f277aff5be1143f371258a2", markers = "python_version >= '3.11' and python_version < '3.12'" }, - { url = "https://github.com/girder/large_image_wheels/raw/wheelhouse/GDAL-3.10.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl#sha256=19cd80ad4bd684e8c7a3f712e5a1c284cce59e5750155f0db0c31d875d3c6321", markers = "python_version >= '3.12' and python_version < '3.13'" }, +[dependency-groups] +dev = [ + "ipdb>=0.13.13", + "ruff>=0.6", ] -ipdb = {version = "^0.13.13", markers = "python_version >= '3.10'"} +[tool.ruff] +line-length = 88 + +[tool.ruff.lint] +select = ["E4", "E7", "E9", "F"] + +[tool.uv] +# Build .venv on the conda python, never a uv-managed one, so +# --system-site-packages sees the conda GDAL. +python-preference = "only-system" [build-system] -requires = ["poetry-core"] -build-backend = "poetry.core.masonry.api" +requires = ["hatchling"] +build-backend = "hatchling.build" + +[tool.hatch.build.targets.wheel] +packages = ["kamera"] diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index 9237998f..00000000 --- a/requirements.txt +++ /dev/null @@ -1,18 +0,0 @@ -numpy -scipy -matplotlib -opencv-python -Pillow -pyyaml -datetime -exifread -matplotlib -numpy -pygeodesy -pyshp -simplekml -shapely -transformations -scriptconfig -ubelt -rich diff --git a/setup.py b/setup.py deleted file mode 100644 index 674e86ae..00000000 --- a/setup.py +++ /dev/null @@ -1,9 +0,0 @@ -from setuptools import setup, find_packages -setup( - name='kamera', - version='0.1', - description="KAMERA: Kitware's Image Acquisition ManagER and Archiver", - author='Adam Romlein', - author_email='adam.romlein@kitware.com', - packages=find_packages("."), - ) diff --git a/uv.lock b/uv.lock new file mode 100644 index 00000000..27ff24e1 --- /dev/null +++ b/uv.lock @@ -0,0 +1,1848 @@ +version = 1 +requires-python = ">=3.10" +resolution-markers = [ + "python_full_version == '3.12.*'", + "python_full_version == '3.11.*'", + "python_full_version < '3.11'", + "python_full_version >= '3.13'", +] + +[[package]] +name = "asttokens" +version = "3.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/be/a5/8e3f9b6771b0b408517c82d97aed8f2036509bc247d46114925e32fe33f0/asttokens-3.0.1.tar.gz", hash = "sha256:71a4ee5de0bde6a31d64f6b13f2293ac190344478f081c3d1bccfcf5eacb0cb7", size = 62308 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d2/39/e7eaf1799466a4aef85b6a4fe7bd175ad2b1c6345066aa33f1f58d4b18d0/asttokens-3.0.1-py3-none-any.whl", hash = "sha256:15a3ebc0f43c2d0a50eeafea25e19046c68398e487b9f1f5b517f7c0f40f976a", size = 27047 }, +] + +[[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 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335 }, +] + +[[package]] +name = "contourpy" +version = "1.3.2" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.11'", +] +dependencies = [ + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/66/54/eb9bfc647b19f2009dd5c7f5ec51c4e6ca831725f1aea7a993034f483147/contourpy-1.3.2.tar.gz", hash = "sha256:b6945942715a034c671b7fc54f9588126b0b8bf23db2696e3ca8328f3ff0ab54", size = 13466130 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/12/a3/da4153ec8fe25d263aa48c1a4cbde7f49b59af86f0b6f7862788c60da737/contourpy-1.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ba38e3f9f330af820c4b27ceb4b9c7feee5fe0493ea53a8720f4792667465934", size = 268551 }, + { url = "https://files.pythonhosted.org/packages/2f/6c/330de89ae1087eb622bfca0177d32a7ece50c3ef07b28002de4757d9d875/contourpy-1.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:dc41ba0714aa2968d1f8674ec97504a8f7e334f48eeacebcaa6256213acb0989", size = 253399 }, + { url = "https://files.pythonhosted.org/packages/c1/bd/20c6726b1b7f81a8bee5271bed5c165f0a8e1f572578a9d27e2ccb763cb2/contourpy-1.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9be002b31c558d1ddf1b9b415b162c603405414bacd6932d031c5b5a8b757f0d", size = 312061 }, + { url = "https://files.pythonhosted.org/packages/22/fc/a9665c88f8a2473f823cf1ec601de9e5375050f1958cbb356cdf06ef1ab6/contourpy-1.3.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8d2e74acbcba3bfdb6d9d8384cdc4f9260cae86ed9beee8bd5f54fee49a430b9", size = 351956 }, + { url = "https://files.pythonhosted.org/packages/25/eb/9f0a0238f305ad8fb7ef42481020d6e20cf15e46be99a1fcf939546a177e/contourpy-1.3.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e259bced5549ac64410162adc973c5e2fb77f04df4a439d00b478e57a0e65512", size = 320872 }, + { url = "https://files.pythonhosted.org/packages/32/5c/1ee32d1c7956923202f00cf8d2a14a62ed7517bdc0ee1e55301227fc273c/contourpy-1.3.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ad687a04bc802cbe8b9c399c07162a3c35e227e2daccf1668eb1f278cb698631", size = 325027 }, + { url = "https://files.pythonhosted.org/packages/83/bf/9baed89785ba743ef329c2b07fd0611d12bfecbedbdd3eeecf929d8d3b52/contourpy-1.3.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:cdd22595308f53ef2f891040ab2b93d79192513ffccbd7fe19be7aa773a5e09f", size = 1306641 }, + { url = "https://files.pythonhosted.org/packages/d4/cc/74e5e83d1e35de2d28bd97033426b450bc4fd96e092a1f7a63dc7369b55d/contourpy-1.3.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b4f54d6a2defe9f257327b0f243612dd051cc43825587520b1bf74a31e2f6ef2", size = 1374075 }, + { url = "https://files.pythonhosted.org/packages/0c/42/17f3b798fd5e033b46a16f8d9fcb39f1aba051307f5ebf441bad1ecf78f8/contourpy-1.3.2-cp310-cp310-win32.whl", hash = "sha256:f939a054192ddc596e031e50bb13b657ce318cf13d264f095ce9db7dc6ae81c0", size = 177534 }, + { url = "https://files.pythonhosted.org/packages/54/ec/5162b8582f2c994721018d0c9ece9dc6ff769d298a8ac6b6a652c307e7df/contourpy-1.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:c440093bbc8fc21c637c03bafcbef95ccd963bc6e0514ad887932c18ca2a759a", size = 221188 }, + { url = "https://files.pythonhosted.org/packages/b3/b9/ede788a0b56fc5b071639d06c33cb893f68b1178938f3425debebe2dab78/contourpy-1.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6a37a2fb93d4df3fc4c0e363ea4d16f83195fc09c891bc8ce072b9d084853445", size = 269636 }, + { url = "https://files.pythonhosted.org/packages/e6/75/3469f011d64b8bbfa04f709bfc23e1dd71be54d05b1b083be9f5b22750d1/contourpy-1.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b7cd50c38f500bbcc9b6a46643a40e0913673f869315d8e70de0438817cb7773", size = 254636 }, + { url = "https://files.pythonhosted.org/packages/8d/2f/95adb8dae08ce0ebca4fd8e7ad653159565d9739128b2d5977806656fcd2/contourpy-1.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d6658ccc7251a4433eebd89ed2672c2ed96fba367fd25ca9512aa92a4b46c4f1", size = 313053 }, + { url = "https://files.pythonhosted.org/packages/c3/a6/8ccf97a50f31adfa36917707fe39c9a0cbc24b3bbb58185577f119736cc9/contourpy-1.3.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:70771a461aaeb335df14deb6c97439973d253ae70660ca085eec25241137ef43", size = 352985 }, + { url = "https://files.pythonhosted.org/packages/1d/b6/7925ab9b77386143f39d9c3243fdd101621b4532eb126743201160ffa7e6/contourpy-1.3.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:65a887a6e8c4cd0897507d814b14c54a8c2e2aa4ac9f7686292f9769fcf9a6ab", size = 323750 }, + { url = "https://files.pythonhosted.org/packages/c2/f3/20c5d1ef4f4748e52d60771b8560cf00b69d5c6368b5c2e9311bcfa2a08b/contourpy-1.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3859783aefa2b8355697f16642695a5b9792e7a46ab86da1118a4a23a51a33d7", size = 326246 }, + { url = "https://files.pythonhosted.org/packages/8c/e5/9dae809e7e0b2d9d70c52b3d24cba134dd3dad979eb3e5e71f5df22ed1f5/contourpy-1.3.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:eab0f6db315fa4d70f1d8ab514e527f0366ec021ff853d7ed6a2d33605cf4b83", size = 1308728 }, + { url = "https://files.pythonhosted.org/packages/e2/4a/0058ba34aeea35c0b442ae61a4f4d4ca84d6df8f91309bc2d43bb8dd248f/contourpy-1.3.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:d91a3ccc7fea94ca0acab82ceb77f396d50a1f67412efe4c526f5d20264e6ecd", size = 1375762 }, + { url = "https://files.pythonhosted.org/packages/09/33/7174bdfc8b7767ef2c08ed81244762d93d5c579336fc0b51ca57b33d1b80/contourpy-1.3.2-cp311-cp311-win32.whl", hash = "sha256:1c48188778d4d2f3d48e4643fb15d8608b1d01e4b4d6b0548d9b336c28fc9b6f", size = 178196 }, + { url = "https://files.pythonhosted.org/packages/5e/fe/4029038b4e1c4485cef18e480b0e2cd2d755448bb071eb9977caac80b77b/contourpy-1.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:5ebac872ba09cb8f2131c46b8739a7ff71de28a24c869bcad554477eb089a878", size = 222017 }, + { url = "https://files.pythonhosted.org/packages/34/f7/44785876384eff370c251d58fd65f6ad7f39adce4a093c934d4a67a7c6b6/contourpy-1.3.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4caf2bcd2969402bf77edc4cb6034c7dd7c0803213b3523f111eb7460a51b8d2", size = 271580 }, + { url = "https://files.pythonhosted.org/packages/93/3b/0004767622a9826ea3d95f0e9d98cd8729015768075d61f9fea8eeca42a8/contourpy-1.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:82199cb78276249796419fe36b7386bd8d2cc3f28b3bc19fe2454fe2e26c4c15", size = 255530 }, + { url = "https://files.pythonhosted.org/packages/e7/bb/7bd49e1f4fa805772d9fd130e0d375554ebc771ed7172f48dfcd4ca61549/contourpy-1.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:106fab697af11456fcba3e352ad50effe493a90f893fca6c2ca5c033820cea92", size = 307688 }, + { url = "https://files.pythonhosted.org/packages/fc/97/e1d5dbbfa170725ef78357a9a0edc996b09ae4af170927ba8ce977e60a5f/contourpy-1.3.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d14f12932a8d620e307f715857107b1d1845cc44fdb5da2bc8e850f5ceba9f87", size = 347331 }, + { url = "https://files.pythonhosted.org/packages/6f/66/e69e6e904f5ecf6901be3dd16e7e54d41b6ec6ae3405a535286d4418ffb4/contourpy-1.3.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:532fd26e715560721bb0d5fc7610fce279b3699b018600ab999d1be895b09415", size = 318963 }, + { url = "https://files.pythonhosted.org/packages/a8/32/b8a1c8965e4f72482ff2d1ac2cd670ce0b542f203c8e1d34e7c3e6925da7/contourpy-1.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f26b383144cf2d2c29f01a1e8170f50dacf0eac02d64139dcd709a8ac4eb3cfe", size = 323681 }, + { url = "https://files.pythonhosted.org/packages/30/c6/12a7e6811d08757c7162a541ca4c5c6a34c0f4e98ef2b338791093518e40/contourpy-1.3.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:c49f73e61f1f774650a55d221803b101d966ca0c5a2d6d5e4320ec3997489441", size = 1308674 }, + { url = "https://files.pythonhosted.org/packages/2a/8a/bebe5a3f68b484d3a2b8ffaf84704b3e343ef1addea528132ef148e22b3b/contourpy-1.3.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3d80b2c0300583228ac98d0a927a1ba6a2ba6b8a742463c564f1d419ee5b211e", size = 1380480 }, + { url = "https://files.pythonhosted.org/packages/34/db/fcd325f19b5978fb509a7d55e06d99f5f856294c1991097534360b307cf1/contourpy-1.3.2-cp312-cp312-win32.whl", hash = "sha256:90df94c89a91b7362e1142cbee7568f86514412ab8a2c0d0fca72d7e91b62912", size = 178489 }, + { url = "https://files.pythonhosted.org/packages/01/c8/fadd0b92ffa7b5eb5949bf340a63a4a496a6930a6c37a7ba0f12acb076d6/contourpy-1.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:8c942a01d9163e2e5cfb05cb66110121b8d07ad438a17f9e766317bcb62abf73", size = 223042 }, + { url = "https://files.pythonhosted.org/packages/2e/61/5673f7e364b31e4e7ef6f61a4b5121c5f170f941895912f773d95270f3a2/contourpy-1.3.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:de39db2604ae755316cb5967728f4bea92685884b1e767b7c24e983ef5f771cb", size = 271630 }, + { url = "https://files.pythonhosted.org/packages/ff/66/a40badddd1223822c95798c55292844b7e871e50f6bfd9f158cb25e0bd39/contourpy-1.3.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3f9e896f447c5c8618f1edb2bafa9a4030f22a575ec418ad70611450720b5b08", size = 255670 }, + { url = "https://files.pythonhosted.org/packages/1e/c7/cf9fdee8200805c9bc3b148f49cb9482a4e3ea2719e772602a425c9b09f8/contourpy-1.3.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:71e2bd4a1c4188f5c2b8d274da78faab884b59df20df63c34f74aa1813c4427c", size = 306694 }, + { url = "https://files.pythonhosted.org/packages/dd/e7/ccb9bec80e1ba121efbffad7f38021021cda5be87532ec16fd96533bb2e0/contourpy-1.3.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:de425af81b6cea33101ae95ece1f696af39446db9682a0b56daaa48cfc29f38f", size = 345986 }, + { url = "https://files.pythonhosted.org/packages/dc/49/ca13bb2da90391fa4219fdb23b078d6065ada886658ac7818e5441448b78/contourpy-1.3.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:977e98a0e0480d3fe292246417239d2d45435904afd6d7332d8455981c408b85", size = 318060 }, + { url = "https://files.pythonhosted.org/packages/c8/65/5245ce8c548a8422236c13ffcdcdada6a2a812c361e9e0c70548bb40b661/contourpy-1.3.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:434f0adf84911c924519d2b08fc10491dd282b20bdd3fa8f60fd816ea0b48841", size = 322747 }, + { url = "https://files.pythonhosted.org/packages/72/30/669b8eb48e0a01c660ead3752a25b44fdb2e5ebc13a55782f639170772f9/contourpy-1.3.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:c66c4906cdbc50e9cba65978823e6e00b45682eb09adbb78c9775b74eb222422", size = 1308895 }, + { url = "https://files.pythonhosted.org/packages/05/5a/b569f4250decee6e8d54498be7bdf29021a4c256e77fe8138c8319ef8eb3/contourpy-1.3.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8b7fc0cd78ba2f4695fd0a6ad81a19e7e3ab825c31b577f384aa9d7817dc3bef", size = 1379098 }, + { url = "https://files.pythonhosted.org/packages/19/ba/b227c3886d120e60e41b28740ac3617b2f2b971b9f601c835661194579f1/contourpy-1.3.2-cp313-cp313-win32.whl", hash = "sha256:15ce6ab60957ca74cff444fe66d9045c1fd3e92c8936894ebd1f3eef2fff075f", size = 178535 }, + { url = "https://files.pythonhosted.org/packages/12/6e/2fed56cd47ca739b43e892707ae9a13790a486a3173be063681ca67d2262/contourpy-1.3.2-cp313-cp313-win_amd64.whl", hash = "sha256:e1578f7eafce927b168752ed7e22646dad6cd9bca673c60bff55889fa236ebf9", size = 223096 }, + { url = "https://files.pythonhosted.org/packages/54/4c/e76fe2a03014a7c767d79ea35c86a747e9325537a8b7627e0e5b3ba266b4/contourpy-1.3.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0475b1f6604896bc7c53bb070e355e9321e1bc0d381735421a2d2068ec56531f", size = 285090 }, + { url = "https://files.pythonhosted.org/packages/7b/e2/5aba47debd55d668e00baf9651b721e7733975dc9fc27264a62b0dd26eb8/contourpy-1.3.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:c85bb486e9be652314bb5b9e2e3b0d1b2e643d5eec4992c0fbe8ac71775da739", size = 268643 }, + { url = "https://files.pythonhosted.org/packages/a1/37/cd45f1f051fe6230f751cc5cdd2728bb3a203f5619510ef11e732109593c/contourpy-1.3.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:745b57db7758f3ffc05a10254edd3182a2a83402a89c00957a8e8a22f5582823", size = 310443 }, + { url = "https://files.pythonhosted.org/packages/8b/a2/36ea6140c306c9ff6dd38e3bcec80b3b018474ef4d17eb68ceecd26675f4/contourpy-1.3.2-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:970e9173dbd7eba9b4e01aab19215a48ee5dd3f43cef736eebde064a171f89a5", size = 349865 }, + { url = "https://files.pythonhosted.org/packages/95/b7/2fc76bc539693180488f7b6cc518da7acbbb9e3b931fd9280504128bf956/contourpy-1.3.2-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c6c4639a9c22230276b7bffb6a850dfc8258a2521305e1faefe804d006b2e532", size = 321162 }, + { url = "https://files.pythonhosted.org/packages/f4/10/76d4f778458b0aa83f96e59d65ece72a060bacb20cfbee46cf6cd5ceba41/contourpy-1.3.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cc829960f34ba36aad4302e78eabf3ef16a3a100863f0d4eeddf30e8a485a03b", size = 327355 }, + { url = "https://files.pythonhosted.org/packages/43/a3/10cf483ea683f9f8ab096c24bad3cce20e0d1dd9a4baa0e2093c1c962d9d/contourpy-1.3.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:d32530b534e986374fc19eaa77fcb87e8a99e5431499949b828312bdcd20ac52", size = 1307935 }, + { url = "https://files.pythonhosted.org/packages/78/73/69dd9a024444489e22d86108e7b913f3528f56cfc312b5c5727a44188471/contourpy-1.3.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:e298e7e70cf4eb179cc1077be1c725b5fd131ebc81181bf0c03525c8abc297fd", size = 1372168 }, + { url = "https://files.pythonhosted.org/packages/0f/1b/96d586ccf1b1a9d2004dd519b25fbf104a11589abfd05484ff12199cca21/contourpy-1.3.2-cp313-cp313t-win32.whl", hash = "sha256:d0e589ae0d55204991450bb5c23f571c64fe43adaa53f93fc902a84c96f52fe1", size = 189550 }, + { url = "https://files.pythonhosted.org/packages/b0/e6/6000d0094e8a5e32ad62591c8609e269febb6e4db83a1c75ff8868b42731/contourpy-1.3.2-cp313-cp313t-win_amd64.whl", hash = "sha256:78e9253c3de756b3f6a5174d024c4835acd59eb3f8e2ca13e775dbffe1558f69", size = 238214 }, + { url = "https://files.pythonhosted.org/packages/33/05/b26e3c6ecc05f349ee0013f0bb850a761016d89cec528a98193a48c34033/contourpy-1.3.2-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:fd93cc7f3139b6dd7aab2f26a90dde0aa9fc264dbf70f6740d498a70b860b82c", size = 265681 }, + { url = "https://files.pythonhosted.org/packages/2b/25/ac07d6ad12affa7d1ffed11b77417d0a6308170f44ff20fa1d5aa6333f03/contourpy-1.3.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:107ba8a6a7eec58bb475329e6d3b95deba9440667c4d62b9b6063942b61d7f16", size = 315101 }, + { url = "https://files.pythonhosted.org/packages/8f/4d/5bb3192bbe9d3f27e3061a6a8e7733c9120e203cb8515767d30973f71030/contourpy-1.3.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:ded1706ed0c1049224531b81128efbd5084598f18d8a2d9efae833edbd2b40ad", size = 220599 }, + { url = "https://files.pythonhosted.org/packages/ff/c0/91f1215d0d9f9f343e4773ba6c9b89e8c0cc7a64a6263f21139da639d848/contourpy-1.3.2-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:5f5964cdad279256c084b69c3f412b7801e15356b16efa9d78aa974041903da0", size = 266807 }, + { url = "https://files.pythonhosted.org/packages/d4/79/6be7e90c955c0487e7712660d6cead01fa17bff98e0ea275737cc2bc8e71/contourpy-1.3.2-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:49b65a95d642d4efa8f64ba12558fcb83407e58a2dfba9d796d77b63ccfcaff5", size = 318729 }, + { url = "https://files.pythonhosted.org/packages/87/68/7f46fb537958e87427d98a4074bcde4b67a70b04900cfc5ce29bc2f556c1/contourpy-1.3.2-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:8c5acb8dddb0752bf252e01a3035b21443158910ac16a3b0d20e7fed7d534ce5", size = 221791 }, +] + +[[package]] +name = "contourpy" +version = "1.3.3" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version == '3.12.*'", + "python_full_version == '3.11.*'", + "python_full_version >= '3.13'", +] +dependencies = [ + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.11.*'" }, + { name = "numpy", version = "2.5.0", 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 } +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 }, +] + +[[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 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl", hash = "sha256:85cef7cff222d8644161529808465972e51340599459b8ac3ccbac5a854e0d30", size = 8321 }, +] + +[[package]] +name = "datetime" +version = "6.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pytz" }, + { name = "zope-interface" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/77/32/decbfd165e9985ba9d8c2d34a39afe5aeba2fc3fe390eb6e9ef1aab98fa8/datetime-6.0.tar.gz", hash = "sha256:c1514936d2f901e10c8e08d83bf04e6c9dbd7ca4f244da94fec980980a3bc4d5", size = 64167 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cf/7a/ea0f3e3ea74be36fc7cf54f966cde732a3de72697983cdb5646b0a4dacde/datetime-6.0-py3-none-any.whl", hash = "sha256:d19988f0657a4e72c9438344157254a8dcad6aea8cd5ae70a5d1b5a75e5dc930", size = 52637 }, +] + +[[package]] +name = "decorator" +version = "5.3.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/60/8b/32f9823da46cde7df2087faa08cd98d01b908f8dcab982cdba9c84e85355/decorator-5.3.1.tar.gz", hash = "sha256:4cbcdd55a6efadb9dbea26b858f4fb3264567b52d69ca0d25b721b553f60ea82", size = 58084 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/05/7f/798705f5296a58ca505d600456748d1be48078eac8a7050d8a98bc9edb89/decorator-5.3.1-py3-none-any.whl", hash = "sha256:f47fe6fdbd2edd623ecfe36875d37aba411624e2670dd395dddae1358689bb3c", size = 10365 }, +] + +[[package]] +name = "exceptiongroup" +version = "1.3.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/50/79/66800aadf48771f6b62f7eb014e352e5d06856655206165d775e675a02c9/exceptiongroup-1.3.1.tar.gz", hash = "sha256:8b412432c6055b0b7d14c310000ae93352ed6754f70fa8f7c34141f91c4e3219", size = 30371 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8a/0e/97c33bf5009bdbac74fd2beace167cab3f978feb69cc36f1ef79360d6c4e/exceptiongroup-1.3.1-py3-none-any.whl", hash = "sha256:a7a39a3bd276781e98394987d3a5701d0c4edffb633bb7a5144577f82c773598", size = 16740 }, +] + +[[package]] +name = "executing" +version = "2.2.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/cc/28/c14e053b6762b1044f34a13aab6859bbf40456d37d23aa286ac24cfd9a5d/executing-2.2.1.tar.gz", hash = "sha256:3632cc370565f6648cc328b32435bd120a1e4ebb20c77e3fdde9a13cd1e533c4", size = 1129488 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c1/ea/53f2148663b321f21b5a606bd5f191517cf40b7072c0497d3c92c4a13b1e/executing-2.2.1-py2.py3-none-any.whl", hash = "sha256:760643d3452b4d777d295bb167ccc74c64a81df23fb5e08eff250c425a4b2017", size = 28317 }, +] + +[[package]] +name = "exifread" +version = "3.5.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e2/4e/d8fce8810d819db47f5b159e75223511c5ccd7ad07c2feca64cf7fab2477/exifread-3.5.1.tar.gz", hash = "sha256:9f998f80d3062741c976dfc4fd033424bc40932937994e4d2181eb70c4b6aedd", size = 56552 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e2/a3/20e34a55c7b225110d3822d07c3cab9e8653d9c179e36783f2ed632a96a7/exifread-3.5.1-py3-none-any.whl", hash = "sha256:e5426ce2857423ad401e575ea9d159dc97449dc041fb6e61b35109caea72c311", size = 59742 }, +] + +[[package]] +name = "fonttools" +version = "4.63.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/84/69/c97f2c18e0db87d2c7b15da1974dace76ae938f1cfa22e2727a648b7ed43/fonttools-4.63.0.tar.gz", hash = "sha256:caeb583deeb5168e694b65cda8b4ee62abedfa66cf88488734466f2366b9c4e0", size = 3597189 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f2/c9/4141c90a90db20f807c7e10bfd689fe53eb8f7f4caff58ee4d4dfe46919f/fonttools-4.63.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e3297a6a4059b4acc3a1e9a8b04741f240a80044eef08ebd32e8b5bcdddce75b", size = 2884632 }, + { url = "https://files.pythonhosted.org/packages/b8/46/ad12b5c10eae602d7ef814b02afa08aacbf89da917fed5b071282b7eadc2/fonttools-4.63.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b1cd75a03ad8cb5bc40c90bfde68c0c47de423aa19e5c0f362b43520645eea94", size = 2429441 }, + { url = "https://files.pythonhosted.org/packages/90/8f/bdca24a84c81d56fffed052229cdcff368f6e05882e526f4558891481f65/fonttools-4.63.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c0425b277a59cff3d80ca42162a8de360f318438a2ac83570842a678d826d579", size = 4946346 }, + { url = "https://files.pythonhosted.org/packages/04/59/a639c0e136441ee91a65b56fdf89e5d075927e7a09c559d1b0f5276577db/fonttools-4.63.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d7e5c9973aa04c95650c96e5f5ad865fbf42d62079163ecfab1e01cbc2504c22", size = 4903184 }, + { url = "https://files.pythonhosted.org/packages/e6/53/91b7e0cb45b536f3da1b29ba8cbab89f27e8b986809e0b1982303a3f4eca/fonttools-4.63.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:cb014d58140a38135f16064c74c652ed57aa0b75cbf8bb59cac821f7edb5334e", size = 4922967 }, + { url = "https://files.pythonhosted.org/packages/c7/b7/87439bf44e6b97c5538cd29d0b7e366a5b8ce2cc132a4134fb67fa3f2fa2/fonttools-4.63.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:032038247a96c1690f9f31e377c389383c902531b085aa4e4dabd6f57f870e69", size = 5042799 }, + { url = "https://files.pythonhosted.org/packages/ad/7c/8b96c3263b89ef99cded544c0f0636686f85dbd3c211c4dceef0231fca23/fonttools-4.63.0-cp310-cp310-win32.whl", hash = "sha256:a8b33a82979e0a6a34ff435cc81317be1f95ec1ebb7a3a2d1c8a6a54f02ae44e", size = 1519704 }, + { url = "https://files.pythonhosted.org/packages/e5/4d/2c2f0069970b6907de8fb5b05c5c0193cc22f717df151d1c7aef1c738f58/fonttools-4.63.0-cp310-cp310-win_amd64.whl", hash = "sha256:0c18358a155d75034911c5ee397a5b44cd19dd325dbb8b35fb60bf421d6a72ac", size = 1568666 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { url = "https://files.pythonhosted.org/packages/a8/99/253e4056e1f0e67b9390125a154b73b5eb73ad521bece95c004858fdeec2/fonttools-4.63.0-cp311-cp311-win32.whl", hash = "sha256:afefc1ed0a59785a7fb06ea7e1678e849c193e1e387db783579bc7b3056fcfcb", size = 2304473 }, + { url = "https://files.pythonhosted.org/packages/08/60/defa5e69641db890a63be281f41345f4c33b157824eaf0b9fad3e08b0dcb/fonttools-4.63.0-cp311-cp311-win_amd64.whl", hash = "sha256:063e08bd17bd5a90127a14123de0d6a952dbc847695fd98b63c043d58057f90c", size = 2356389 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { url = "https://files.pythonhosted.org/packages/62/f2/aa27c7f98db5b064883dadcc5283947e81e034de42e22a33675878d98b54/fonttools-4.63.0-cp312-cp312-win32.whl", hash = "sha256:af2fd1664d00a397d75f806985ddb36282091c2131a73a6485c23b4a34722263", size = 2292575 }, + { url = "https://files.pythonhosted.org/packages/87/36/cccb9bc2a6ab63d1b2980374f0dca72ce95ae267c9b4cfe77455bb70d0d4/fonttools-4.63.0-cp312-cp312-win_amd64.whl", hash = "sha256:59ac449f8cca9b4ffa08d2e7bbadad87ce710d69d1eda5c3c1ce579baa987272", size = 2343211 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { url = "https://files.pythonhosted.org/packages/d8/1f/a98a30a814b9ddef3a2e706025f90b9e0bc94890e6cb15254bc86547d11a/fonttools-4.63.0-cp313-cp313-win32.whl", hash = "sha256:85be818f5506e8a7753153def2c9550178f0ecae6a47b5e0e8dbb23f7cc90380", size = 2291313 }, + { url = "https://files.pythonhosted.org/packages/92/46/5177b01f3b4abfdd4409f31cca4ab279c9343a26efbe9ec78c97fc612e02/fonttools-4.63.0-cp313-cp313-win_amd64.whl", hash = "sha256:ba04cb5891d4c0c21b6da95eda8d7b090021508a294fff33464fc7d241e0856b", size = 2342299 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { url = "https://files.pythonhosted.org/packages/de/40/3ae22b60ff1d41ce0bd044b31238cdc72cef99f28b976f1e128ebd618c9b/fonttools-4.63.0-cp314-cp314-win32.whl", hash = "sha256:22693918177bd9ceabec4736d338045f357769416fc6b0b2508eefef75b08616", size = 2295026 }, + { url = "https://files.pythonhosted.org/packages/c3/d4/98078064ccc76b45cb0f6c002452011e93c4bd26f6850344f0951cc1fe89/fonttools-4.63.0-cp314-cp314-win_amd64.whl", hash = "sha256:7d782fac32985914c351556f68ac0855391572bcd87de50e05970d3cd4c96fc5", size = 2347454 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { url = "https://files.pythonhosted.org/packages/34/3b/214dcc19ee31d3d38fb5ad2755c11ef0514e5dc300bbaf41c0b69f393799/fonttools-4.63.0-cp314-cp314t-win32.whl", hash = "sha256:948428a275741f0b64b113c955425a953314f4b9ab9997f73a72c83e68e569c8", size = 2359326 }, + { url = "https://files.pythonhosted.org/packages/dd/1e/3ff1a9b523058c2eeb6a9d50f5574e2a738200d0d94107d5bc4105e8da3f/fonttools-4.63.0-cp314-cp314t-win_amd64.whl", hash = "sha256:6d4741eb179121cab9eea4cb2393d24492373a260d7945006358c08cfbf45419", size = 2425829 }, + { url = "https://files.pythonhosted.org/packages/2c/47/c99d5268f354002ce80f8d029cd9d7d872969da1de8b93d32de4dc56d6f4/fonttools-4.63.0-py3-none-any.whl", hash = "sha256:445af2eab030a16b9171ea8bdda7ebf7d96bda2df88ee182a464252f6e05e20d", size = 1164562 }, +] + +[[package]] +name = "ipdb" +version = "0.13.13" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "decorator" }, + { name = "ipython", version = "8.39.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "ipython", version = "9.15.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "tomli", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/3d/1b/7e07e7b752017f7693a0f4d41c13e5ca29ce8cbcfdcc1fd6c4ad8c0a27a0/ipdb-0.13.13.tar.gz", hash = "sha256:e3ac6018ef05126d442af680aad863006ec19d02290561ac88b8b1c0b0cfc726", size = 17042 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0c/4c/b075da0092003d9a55cf2ecc1cae9384a1ca4f650d51b00fc59875fe76f6/ipdb-0.13.13-py3-none-any.whl", hash = "sha256:45529994741c4ab6d2388bfa5d7b725c2cf7fe9deffabdb8a6113aa5ed449ed4", size = 12130 }, +] + +[[package]] +name = "ipython" +version = "8.39.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.11'", +] +dependencies = [ + { name = "colorama", marker = "python_full_version < '3.11' and sys_platform == 'win32'" }, + { name = "decorator", marker = "python_full_version < '3.11'" }, + { name = "exceptiongroup", marker = "python_full_version < '3.11'" }, + { name = "jedi", marker = "python_full_version < '3.11'" }, + { name = "matplotlib-inline", marker = "python_full_version < '3.11'" }, + { name = "pexpect", marker = "python_full_version < '3.11' and sys_platform != 'emscripten' and sys_platform != 'win32'" }, + { name = "prompt-toolkit", marker = "python_full_version < '3.11'" }, + { name = "pygments", marker = "python_full_version < '3.11'" }, + { name = "stack-data", marker = "python_full_version < '3.11'" }, + { name = "traitlets", marker = "python_full_version < '3.11'" }, + { name = "typing-extensions", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/40/18/f8598d287006885e7136451fdea0755af4ebcbfe342836f24deefaed1164/ipython-8.39.0.tar.gz", hash = "sha256:4110ae96012c379b8b6db898a07e186c40a2a1ef5d57a7fa83166047d9da7624", size = 5513971 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c0/56/4cc7fc9e9e3f38fd324f24f8afe0ad8bb5fa41283f37f1aaf9de0612c968/ipython-8.39.0-py3-none-any.whl", hash = "sha256:bb3c51c4fa8148ab1dea07a79584d1c854e234ea44aa1283bcb37bc75054651f", size = 831849 }, +] + +[[package]] +name = "ipython" +version = "9.15.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version == '3.12.*'", + "python_full_version == '3.11.*'", + "python_full_version >= '3.13'", +] +dependencies = [ + { name = "colorama", marker = "python_full_version >= '3.11' and sys_platform == 'win32'" }, + { name = "decorator", marker = "python_full_version >= '3.11'" }, + { name = "ipython-pygments-lexers", marker = "python_full_version >= '3.11'" }, + { name = "jedi", marker = "python_full_version >= '3.11'" }, + { name = "matplotlib-inline", marker = "python_full_version >= '3.11'" }, + { name = "pexpect", marker = "python_full_version >= '3.11' and sys_platform != 'emscripten' and sys_platform != 'win32'" }, + { name = "prompt-toolkit", marker = "python_full_version >= '3.11'" }, + { name = "psutil", marker = "python_full_version >= '3.11' and sys_platform != 'cygwin' and sys_platform != 'emscripten'" }, + { name = "pygments", marker = "python_full_version >= '3.11'" }, + { name = "stack-data", marker = "python_full_version >= '3.11'" }, + { name = "traitlets", marker = "python_full_version >= '3.11'" }, + { name = "typing-extensions", marker = "python_full_version == '3.11.*'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/53/59/165d3b4d75cc34add3122c4417ecb229085140ac573103c223cd01dde96f/ipython-9.15.0.tar.gz", hash = "sha256:da2819ce2aa83135257df830660b1176d986c3d2876db24df01974fa955b2756", size = 4442580 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/40/3a/948263ca3b9d65bb2b1b0c521b3a49fad5d59ada58724bd87d2bd5ff3f36/ipython-9.15.0-py3-none-any.whl", hash = "sha256:515ad9c3cdf0c932a5a9f6245419e8aba706b7bd03c3e1d3a1c83d9351d6aa6e", size = 630895 }, +] + +[[package]] +name = "ipython-pygments-lexers" +version = "1.1.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pygments", marker = "python_full_version >= '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ef/4c/5dd1d8af08107f88c7f741ead7a40854b8ac24ddf9ae850afbcf698aa552/ipython_pygments_lexers-1.1.1.tar.gz", hash = "sha256:09c0138009e56b6854f9535736f4171d855c8c08a563a0dcd8022f78355c7e81", size = 8393 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d9/33/1f075bf72b0b747cb3288d011319aaf64083cf2efef8354174e3ed4540e2/ipython_pygments_lexers-1.1.1-py3-none-any.whl", hash = "sha256:a9462224a505ade19a605f71f8fa63c2048833ce50abc86768a0d81d876dc81c", size = 8074 }, +] + +[[package]] +name = "jedi" +version = "0.20.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "parso" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/46/b7/a3635f6a2d7cf5b5dd98064fc1d5fbbafcb25477bcea204a3a92145d158b/jedi-0.20.0.tar.gz", hash = "sha256:c3f4ccbd276696f4b19c54618d4fb18f9fc24b0aef02acf704b23f487daa1011", size = 3119416 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9a/93/242e2eab5fe682ffcb8b0084bde703a41d51e17ee0f3a31ff0d9d813620a/jedi-0.20.0-py2.py3-none-any.whl", hash = "sha256:7bdd9c2634f56713299976f4cbd59cb3fa92165cc5e05ea811fb253480728b67", size = 4884812 }, +] + +[[package]] +name = "kamera" +version = "0.1.0" +source = { editable = "." } +dependencies = [ + { name = "datetime" }, + { name = "exifread" }, + { name = "matplotlib", version = "3.10.9", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "matplotlib", version = "3.11.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.11.*'" }, + { name = "numpy", version = "2.5.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, + { name = "opencv-python" }, + { name = "pillow" }, + { name = "pygeodesy" }, + { name = "pyshp" }, + { name = "pyyaml" }, + { name = "rich" }, + { name = "scipy", version = "1.15.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "scipy", version = "1.17.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.11.*'" }, + { name = "scipy", version = "1.18.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, + { name = "scriptconfig" }, + { name = "shapely" }, + { name = "simplekml" }, + { name = "transformations", version = "2025.1.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "transformations", version = "2026.1.18", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "ubelt" }, +] + +[package.dev-dependencies] +dev = [ + { name = "ipdb" }, + { name = "ruff" }, +] + +[package.metadata] +requires-dist = [ + { name = "datetime", specifier = ">=5.5" }, + { name = "exifread", specifier = ">=3.0.0" }, + { name = "matplotlib", specifier = ">=3.9.2" }, + { name = "numpy", specifier = ">=2.1.1" }, + { name = "opencv-python", specifier = ">=4.10.0.84" }, + { name = "pillow", specifier = ">=10.4.0" }, + { name = "pygeodesy", specifier = ">=24.9.29" }, + { name = "pyshp", specifier = ">=2.3.1" }, + { name = "pyyaml", specifier = ">=6.0.2" }, + { name = "rich", specifier = ">=13.9.1" }, + { name = "scipy", specifier = ">=1.14.1" }, + { name = "scriptconfig", specifier = ">=0.8.0" }, + { name = "shapely", specifier = ">=2.0.6" }, + { name = "simplekml", specifier = ">=1.3.6" }, + { name = "transformations", specifier = ">=2024.5.24" }, + { name = "ubelt", specifier = ">=1.3.6" }, +] + +[package.metadata.requires-dev] +dev = [ + { name = "ipdb", specifier = ">=0.13.13" }, + { name = "ruff", specifier = ">=0.6" }, +] + +[[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/ac/f8/06549565caa026e540b7e7bab5c5a90eb7ca986015f4c48dace243cd24d9/kiwisolver-1.5.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:32cc0a5365239a6ea0c6ed461e8838d053b57e397443c0ca894dcc8e388d4374", size = 122802 }, + { url = "https://files.pythonhosted.org/packages/84/eb/8476a0818850c563ff343ea7c9c05dcdcbd689a38e01aa31657df01f91fa/kiwisolver-1.5.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:cc0b66c1eec9021353a4b4483afb12dfd50e3669ffbb9152d6842eb34c7e29fd", size = 66216 }, + { url = "https://files.pythonhosted.org/packages/f3/c4/f9c8a6b4c21aed4198566e45923512986d6cef530e7263b3a5f823546561/kiwisolver-1.5.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:86e0287879f75621ae85197b0877ed2f8b7aa57b511c7331dce2eb6f4de7d476", size = 63917 }, + { url = "https://files.pythonhosted.org/packages/f1/0e/ba4ae25d03722f64de8b2c13e80d82ab537a06b30fc7065183c6439357e3/kiwisolver-1.5.0-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:62f59da443c4f4849f73a51a193b1d9d258dcad0c41bc4d1b8fb2bcc04bfeb22", size = 1628776 }, + { url = "https://files.pythonhosted.org/packages/8a/e4/3f43a011bc8a0860d1c96f84d32fa87439d3feedf66e672fef03bf5e8bac/kiwisolver-1.5.0-cp310-cp310-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9190426b7aa26c5229501fa297b8d0653cfd3f5a36f7990c264e157cbf886b3b", size = 1228164 }, + { url = "https://files.pythonhosted.org/packages/4b/34/3a901559a1e0c218404f9a61a93be82d45cb8f44453ba43088644980f033/kiwisolver-1.5.0-cp310-cp310-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c8277104ded0a51e699c8c3aff63ce2c56d4ed5519a5f73e0fd7057f959a2b9e", size = 1246656 }, + { url = "https://files.pythonhosted.org/packages/87/9e/f78c466ea20527822b95ad38f141f2de1dcd7f23fb8716b002b0d91bbe59/kiwisolver-1.5.0-cp310-cp310-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8f9baf6f0a6e7571c45c8863010b45e837c3ee1c2c77fcd6ef423be91b21fedb", size = 1295562 }, + { url = "https://files.pythonhosted.org/packages/0a/66/fd0e4a612e3a286c24e6d6f3a5428d11258ed1909bc530ba3b59807fd980/kiwisolver-1.5.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:cff8e5383db4989311f99e814feeb90c4723eb4edca425b9d5d9c3fefcdd9537", size = 2178473 }, + { url = "https://files.pythonhosted.org/packages/dc/8e/6cac929e0049539e5ee25c1ee937556f379ba5204840d03008363ced662d/kiwisolver-1.5.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:ebae99ed6764f2b5771c522477b311be313e8841d2e0376db2b10922daebbba4", size = 2274035 }, + { url = "https://files.pythonhosted.org/packages/ca/d3/9d0c18f1b52ea8074b792452cf17f1f5a56bd0302a85191f405cfbf9da16/kiwisolver-1.5.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:d5cd5189fc2b6a538b75ae45433140c4823463918f7b1617c31e68b085c0022c", size = 2443217 }, + { url = "https://files.pythonhosted.org/packages/45/2a/6e19368803a038b2a90857bf4ee9e3c7b667216d045866bf22d3439fd75e/kiwisolver-1.5.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:f42c23db5d1521218a3276bb08666dcb662896a0be7347cba864eca45ff64ede", size = 2249196 }, + { url = "https://files.pythonhosted.org/packages/75/2b/3f641dfcbe72e222175d626bacf2f72c3b34312afec949dd1c50afa400f5/kiwisolver-1.5.0-cp310-cp310-win_amd64.whl", hash = "sha256:94eff26096eb5395136634622515b234ecb6c9979824c1f5004c6e3c3c85ccd2", size = 73389 }, + { url = "https://files.pythonhosted.org/packages/da/88/299b137b9e0025d8982e03d2d52c123b0a2b159e84b0ef1501ef446339cf/kiwisolver-1.5.0-cp310-cp310-win_arm64.whl", hash = "sha256:dd952e03bfbb096cfe2dd35cd9e00f269969b67536cb4370994afc20ff2d0875", size = 64782 }, + { 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/1c/fa/2910df836372d8761bb6eff7d8bdcb1613b5c2e03f260efe7abe34d388a7/kiwisolver-1.5.0-graalpy312-graalpy250_312_native-macosx_10_13_x86_64.whl", hash = "sha256:5ae8e62c147495b01a0f4765c878e9bfdf843412446a247e28df59936e99e797", size = 130262 }, + { 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 }, + { 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 }, + { 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 }, + { url = "https://files.pythonhosted.org/packages/17/6f/6fd4f690a40c2582fa34b97d2678f718acf3706b91d270c65ecb455d0a06/kiwisolver-1.5.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:295d9ffe712caa9f8a3081de8d32fc60191b4b51c76f02f951fd8407253528f4", size = 59606 }, + { url = "https://files.pythonhosted.org/packages/82/a0/2355d5e3b338f13ce63f361abb181e3b6ea5fffdb73f739b3e80efa76159/kiwisolver-1.5.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:51e8c4084897de9f05898c2c2a39af6318044ae969d46ff7a34ed3f96274adca", size = 57537 }, + { url = "https://files.pythonhosted.org/packages/c8/b9/1d50e610ecadebe205b71d6728fd224ce0e0ca6aba7b9cbe1da049203ac5/kiwisolver-1.5.0-pp310-pypy310_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:b83af57bdddef03c01a9138034c6ff03181a3028d9a1003b301eb1a55e161a3f", size = 79888 }, + { url = "https://files.pythonhosted.org/packages/cd/ee/b85ffcd75afed0357d74f0e6fc02a4507da441165de1ca4760b9f496390d/kiwisolver-1.5.0-pp310-pypy310_pp73-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bf4679a3d71012a7c2bf360e5cd878fbd5e4fcac0896b56393dec239d81529ed", size = 77584 }, + { url = "https://files.pythonhosted.org/packages/6b/dd/644d0dde6010a8583b4cd66dd41c5f83f5325464d15c4f490b3340ab73b4/kiwisolver-1.5.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:41024ed50e44ab1a60d3fe0a9d15a4ccc9f5f2b1d814ff283c8d01134d5b81bc", size = 73390 }, + { 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 }, +] + +[[package]] +name = "markdown-it-py" +version = "4.2.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mdurl" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/06/ff/7841249c247aa650a76b9ee4bbaeae59370dc8bfd2f6c01f3630c35eb134/markdown_it_py-4.2.0.tar.gz", hash = "sha256:04a21681d6fbb623de53f6f364d352309d4094dd4194040a10fd51833e418d49", size = 82454 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b3/81/4da04ced5a082363ecfa159c010d200ecbd959ae410c10c0264a38cac0f5/markdown_it_py-4.2.0-py3-none-any.whl", hash = "sha256:9f7ebbcd14fe59494226453aed97c1070d83f8d24b6fc3a3bcf9a38092641c4a", size = 91687 }, +] + +[[package]] +name = "matplotlib" +version = "3.10.9" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.11'", +] +dependencies = [ + { name = "contourpy", version = "1.3.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "cycler", marker = "python_full_version < '3.11'" }, + { name = "fonttools", marker = "python_full_version < '3.11'" }, + { name = "kiwisolver", marker = "python_full_version < '3.11'" }, + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "packaging", marker = "python_full_version < '3.11'" }, + { name = "pillow", marker = "python_full_version < '3.11'" }, + { name = "pyparsing", marker = "python_full_version < '3.11'" }, + { name = "python-dateutil", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/63/1b/4be5be87d43d327a0cf4de1a56e86f7f84c89312452406cf122efe2839e6/matplotlib-3.10.9.tar.gz", hash = "sha256:fd66508e8c6877d98e586654b608a0456db8d7e8a546eb1e2600efd957302358", size = 34811233 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/18/6f/340b04986e67aac6f66c5145ce68bf72c64bed30f92c8913499a6e6b8f99/matplotlib-3.10.9-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:77210dce9cb8153dffc967efaae990543392563d5a376d4dd8539bebcb0ed217", size = 8296625 }, + { url = "https://files.pythonhosted.org/packages/bb/2f/127081eb83162053ebb9678ceac64220b93a663e0167432566e9c7c82aab/matplotlib-3.10.9-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1e7698ac9868428e84d2c967424803b2472ff7167d9d6590d4204ed775343c3b", size = 8188790 }, + { url = "https://files.pythonhosted.org/packages/fc/b7/d8bcec2626c35f96972bff656299fef4578113ea6193c8fdad324710410c/matplotlib-3.10.9-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1aa972116abb4c9d201bf245620b433726cb6856f3bef6a78f776a00f5c92d37", size = 8769389 }, + { url = "https://files.pythonhosted.org/packages/12/49/b78e214a527ea732033b7f4d37f7afb504d74ba9d134bd47938230dfb8b1/matplotlib-3.10.9-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ae2f11957b27ce53497dd4d7b235c4d4f1faf383dfb39d0c5beb833bff883294", size = 9589657 }, + { url = "https://files.pythonhosted.org/packages/5f/15/5246f7b43beae19c74dfee651d58d6cc8112e06f77adb4e88cc04f2e3a23/matplotlib-3.10.9-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b049278ddce116aaa1c1377ebf58adea909132dfce0281cf7e3a1ea9fc2e2c65", size = 9651983 }, + { url = "https://files.pythonhosted.org/packages/75/77/5acecfe672ba0fa1b8c0454f69ce155d1e6fc5852fa7206bf9afaf767121/matplotlib-3.10.9-cp310-cp310-win_amd64.whl", hash = "sha256:82834c3c292d24d3a8aae77cd2d20019de69d692a34a970e4fdb8d33e2ea3dda", size = 8199701 }, + { url = "https://files.pythonhosted.org/packages/4c/8c/290f021104741fea63769c31494f5324c0cd249bf536a65a4350767b1f22/matplotlib-3.10.9-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:68cfdcede415f7c8f5577b03303dd94526cdb6d11036cecdc205e08733b2d2bb", size = 8306860 }, + { url = "https://files.pythonhosted.org/packages/51/18/325cd32ece1120d1da51cc4e4294c6580190699490183fc2fe8cb6d61ec5/matplotlib-3.10.9-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:dfca0129678bd56379db26c52b5d77ed7de314c047492fbdc763aa7501710cfb", size = 8199254 }, + { url = "https://files.pythonhosted.org/packages/79/db/e28c1b83e3680740aa78925f5fb2ae4d16207207419ad75ea9fe604f8676/matplotlib-3.10.9-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8e436d155fa8a3399dc62683f8f5d0e2e50d25d0144a73edd73f82eec8f4abfb", size = 8777092 }, + { url = "https://files.pythonhosted.org/packages/55/fa/3ce7adfe9ba101748f465211660d9c6374c876b671bdb8c2bb6d347e8b94/matplotlib-3.10.9-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:56fc0bd271b00025c6edfdc7c2dcd247372c8e1544971d62e1dc7c17367e8bf9", size = 9595691 }, + { url = "https://files.pythonhosted.org/packages/36/c4/6960a76686ed668f2c60f84e9799ba4c0d56abdb36b1577b60c1d061d1ec/matplotlib-3.10.9-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a5a6104ed666402ba5106d7f36e0e0cdca4e8d7fa4d39708ca88019e2835a2eb", size = 9659771 }, + { url = "https://files.pythonhosted.org/packages/7e/0d/271aace3342157c64700c9ff4c59c7b392f3dbab393692e8db6fbe7ab96c/matplotlib-3.10.9-cp311-cp311-win_amd64.whl", hash = "sha256:d730e984eddf56974c3e72b6129c7ca462ac38dc624338f4b0b23eb23ecba00f", size = 8205112 }, + { url = "https://files.pythonhosted.org/packages/e2/ee/cb57ad4754f3e7b9174ce6ce66d9205fb827067e48a9f58ac09d7e7d6b77/matplotlib-3.10.9-cp311-cp311-win_arm64.whl", hash = "sha256:51bf0ddbdc598e060d46c16b5590708f81a1624cefbaaf62f6a81bf9285b8c80", size = 8132310 }, + { url = "https://files.pythonhosted.org/packages/35/c6/5581e26c72233ebb2a2a6fed2d24fb7c66b4700120b813f51b0555acf0b6/matplotlib-3.10.9-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f0c3c28d9fbcc1fe7a03be236d73430cf6409c41fb2383a7ac52fe932b072cb1", size = 8319908 }, + { url = "https://files.pythonhosted.org/packages/b7/18/4880dd762e40cd360c1bf06e890c5a97b997e91cb324602b1a19950ad5ce/matplotlib-3.10.9-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:41cb28c2bd769aa3e98322c6ab09854cbcc52ab69d2759d681bba3e327b2b320", size = 8216016 }, + { url = "https://files.pythonhosted.org/packages/32/91/d024616abdba99e83120e07a20658976f6a343646710760c4a51df126029/matplotlib-3.10.9-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ae20801130378b82d647ff5047c07316295b68dc054ca6b3c13519d0ea624285", size = 8789336 }, + { url = "https://files.pythonhosted.org/packages/5c/04/030a2f61ef2158f5e4c259487a92ac877732499fb33d871585d89e03c42d/matplotlib-3.10.9-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6c63ebcd8b4b169eb2f5c200552ae6b8be8999a005b6b507ed76fb8d7d674fe2", size = 9604602 }, + { url = "https://files.pythonhosted.org/packages/fc/c2/541e4d09d87bb6b5830fc28b4c887a9a8cf4e1c6cee698a8c05552ae2003/matplotlib-3.10.9-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:d75d11c949914165976c621b2324f9ef162af7ebf4b057ddf95dd1dba7e5edcf", size = 9670966 }, + { url = "https://files.pythonhosted.org/packages/04/a1/4571fc46e7702de8d0c2dc54ad1b2f8e29328dea3ee90831181f7353d93c/matplotlib-3.10.9-cp312-cp312-win_amd64.whl", hash = "sha256:d091f9d758b34aaaaa6331d13574bf01891d903b3dec59bfff458ef7551de5d6", size = 8217462 }, + { url = "https://files.pythonhosted.org/packages/4b/d0/2269edb12aa30c13c8bcc9382892e39943ce1d28aab4ec296e0381798e81/matplotlib-3.10.9-cp312-cp312-win_arm64.whl", hash = "sha256:10cc5ce06d10231c36f40e875f3c7e8050362a4ee8f0ee5d29a6b3277d57bb42", size = 8136688 }, + { url = "https://files.pythonhosted.org/packages/aa/d3/8d4f6afbecb49fc04e060a57c0fce39ea51cc163a6bd87303ccd698e4fa6/matplotlib-3.10.9-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b580440f1ff81a0e34122051a3dfabb7e4b7f9e380629929bde0eff9af72165f", size = 8320331 }, + { url = "https://files.pythonhosted.org/packages/63/d9/9e14bc7564bf92d5ffa801ae5fac819ce74b925dfb55e3ebde61a3bbad3e/matplotlib-3.10.9-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:b1b745c489cd1a77a0dc1120a05dc87af9798faebc913601feb8c73d89bf2d1e", size = 8216461 }, + { url = "https://files.pythonhosted.org/packages/8a/17/4402d0d14ccf1dfc70932600b68097fbbf9c898a4871d2cbbe79c7801a32/matplotlib-3.10.9-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8f3bcac1ca5ed000a6f4337d47ba67dfddf37ed6a46c15fd7f014997f7bf865f", size = 8790091 }, + { url = "https://files.pythonhosted.org/packages/3e/0b/322aeec06dd9b91411f92028b37d447342770a24392aa4813e317064dad5/matplotlib-3.10.9-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7a8d66a55def891c33147ba3ba9bfcabf0b526a43764c818acbb4525e5ed0838", size = 9605027 }, + { url = "https://files.pythonhosted.org/packages/74/88/5f13482f55e7b00bcfc09838b093c2456e1379978d2a146844aae05350ad/matplotlib-3.10.9-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:d843374407c4017a6403b59c6c81606773d136f3259d5b6da3131bc814542cc2", size = 9671269 }, + { url = "https://files.pythonhosted.org/packages/c5/e0/0840fd2f93da988ec660b8ad1984abe9f25d2aed22a5e394ff1c68c88307/matplotlib-3.10.9-cp313-cp313-win_amd64.whl", hash = "sha256:f4399f64b3e94cd500195490972ae1ee81170df1636fa15364d157d5bdd7b921", size = 8217588 }, + { url = "https://files.pythonhosted.org/packages/47/b9/d706d06dd605c49b9f83a2aed8c13e3e5db70697d7a80b7e3d7915de6b17/matplotlib-3.10.9-cp313-cp313-win_arm64.whl", hash = "sha256:ba7b3b8ef09eab7df0e86e9ae086faa433efbfbdb46afcb3aa16aabf779469a8", size = 8136913 }, + { url = "https://files.pythonhosted.org/packages/9b/45/6e32d96978264c8ca8c4b1010adb955a1a49cfaf314e212bbc8908f04a61/matplotlib-3.10.9-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:09218df8a93712bd6ea133e83a153c755448cf7868316c531cffcc43f69d1cc9", size = 8368019 }, + { url = "https://files.pythonhosted.org/packages/86/0a/c8e3d3bba245f0f7fc424937f8ff7ef77291a36af3edb97ccd78aa93d84f/matplotlib-3.10.9-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:82368699727bfb7b0182e1aa13082e3c08e092fa1a25d3e1fd92405bff96f6d4", size = 8264645 }, + { url = "https://files.pythonhosted.org/packages/3d/aa/5bf5a14fe4fed73a4209a155606f8096ff797aad89c6c35179026571133e/matplotlib-3.10.9-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3225f4e1edcb8c86c884ddf79ebe20ecd0a67d30188f279897554ccd8fded4dc", size = 8802194 }, + { url = "https://files.pythonhosted.org/packages/dd/5e/b4be852d6bba6fd15893fadf91ff26ae49cb91aac789e95dde9d342e664f/matplotlib-3.10.9-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:de2445a0c6690d21b7eb6ce071cebad6d40a2e9bdf10d039074a96ba19797b99", size = 9622684 }, + { url = "https://files.pythonhosted.org/packages/4c/3d/ed428c971139112ef730f62770654d609467346d09d4b62617e1afd68a5a/matplotlib-3.10.9-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:b2b9516251cb89ff618d757daec0e2ed1bf21248013844a853d87ef85ab3081d", size = 9680790 }, + { url = "https://files.pythonhosted.org/packages/e7/09/052e884aaf2b985c63cb79f715f1d5b6a3eaa7de78f6a52b9dbc077d5b53/matplotlib-3.10.9-cp313-cp313t-win_amd64.whl", hash = "sha256:e9fae004b941b23ff2edcf1567a857ed77bafc8086ffa258190462328434faf8", size = 8287571 }, + { url = "https://files.pythonhosted.org/packages/f4/38/ae27288e788c35a4250491422f3db7750366fc8c97d6f36fbdecfc1f5518/matplotlib-3.10.9-cp313-cp313t-win_arm64.whl", hash = "sha256:6b63d9c7c769b88ab81e10dc86e4e0607cf56817b9f9e6cf24b2a5f1693b8e38", size = 8188292 }, + { url = "https://files.pythonhosted.org/packages/d6/e6/3bd8afd04949f02eabc1c17115ea5255e19cacd4d06fc5abdde4eeb0052c/matplotlib-3.10.9-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:172db52c9e683f5d12eaf57f0f54834190e12581fe1cc2a19595a8f5acb4e77d", size = 8321276 }, + { url = "https://files.pythonhosted.org/packages/41/86/86231232fff41c9f8e4a1a7d7a597d349a02527109c3af7d618366122139/matplotlib-3.10.9-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:97e35e8d39ccc85859095e01a53847432ba9a53ddf7986f7a54a11b73d0e143f", size = 8218218 }, + { url = "https://files.pythonhosted.org/packages/85/8f/becc9722cafc64f5d2eb0b7c1bf5f585271c618a45dbd8fabeb021f898b6/matplotlib-3.10.9-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:aba1615dabe83188e19d4f75a253c6a08423e04c1425e64039f800050a69de6b", size = 9608145 }, + { url = "https://files.pythonhosted.org/packages/32/5d/f7e914f7d9325abff4057cee62c0fa70263683189f774473cbfb534cd13b/matplotlib-3.10.9-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:34cf8167e023ad956c15f36302911d5406bd99a9862c1a8499ea6f7c0e015dc2", size = 9885085 }, + { url = "https://files.pythonhosted.org/packages/a5/fd/fa69f2221534e80cc5772ac2b7d222011a2acafc2ec7216d5dd174c864ae/matplotlib-3.10.9-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:59476c6d29d612b8e9bb6ce8c5b631be6ba8f9e3a2421f22a02b192c7dd28716", size = 9672358 }, + { url = "https://files.pythonhosted.org/packages/ab/1a/5a4f747a8b271cbb024946d2dd3c913ab5032ba430626f8c3528ada96b4b/matplotlib-3.10.9-cp314-cp314-win_amd64.whl", hash = "sha256:336b9acc64d309063126edcdaca00db9373af3c476bb94388fe9c5a53ad13e6f", size = 8349970 }, + { url = "https://files.pythonhosted.org/packages/64/dc/95d60ecaefe30680a154b52ea96ab4b0dab547f1fd6aa12f5fb655e89cae/matplotlib-3.10.9-cp314-cp314-win_arm64.whl", hash = "sha256:2dc9477819ffd78ad12a20df1d9d6a6bd4fec6aaa9072681465fddca052f1456", size = 8272785 }, + { url = "https://files.pythonhosted.org/packages/70/a0/005d68bc8b8418300ce6591f18586910a8526806e2ab663933d9f20a41e9/matplotlib-3.10.9-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:da4e09638420548f31c354032a6250e473c68e5a4e96899b4844cf39ddea23fe", size = 8367999 }, + { url = "https://files.pythonhosted.org/packages/22/05/1236cc9290be70b2498af20ca348add76e3fffe7f67b477db5133a84f3ea/matplotlib-3.10.9-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:345f6f68ecc8da0ca56fad2ea08fde1a115eda530079eca185d50a7bc3e146c6", size = 8264543 }, + { url = "https://files.pythonhosted.org/packages/cd/c2/071f5a5ff6c5bd63aaaf2f45c811d9bf2ced94bde188d9e1a519e21d0cba/matplotlib-3.10.9-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4edcfbd8565339aa62f1cd4012f7180926fdbe71850f7b0d3c379c175cd6b66c", size = 9622800 }, + { url = "https://files.pythonhosted.org/packages/95/57/da7d1f10a85624b9e7db68e069dd94e58dc41dbf9463c5921632ecbe3661/matplotlib-3.10.9-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6be157fe17fc37cb95ac1d7374cf717ce9259616edec911a78d9d26dae8522d4", size = 9888561 }, + { url = "https://files.pythonhosted.org/packages/67/b2/ef8d6bb59b0edb6c16c968b70f548aa13b54348972def5aa6ac85df67145/matplotlib-3.10.9-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:4e42042d54db34fda4e95a7bd3e5789c2a995d2dad3eb8850232ee534092fbbf", size = 9680884 }, + { url = "https://files.pythonhosted.org/packages/61/1c/d21bfeb9931881ebe96bcfcff27c7ae4b160ae0ec291a714c42641a56d75/matplotlib-3.10.9-cp314-cp314t-win_amd64.whl", hash = "sha256:c27df8b3848f32a83d1767566595e43cfaa4460380974da06f4279a7ec143c39", size = 8432333 }, + { url = "https://files.pythonhosted.org/packages/78/23/92493c3e6e1b635ccfff146f7b99e674808787915420373ac399283764c2/matplotlib-3.10.9-cp314-cp314t-win_arm64.whl", hash = "sha256:a49f1eadc84ca85fd72fa4e89e70e61bf86452df6f971af04b12c60761a0772c", size = 8324785 }, + { url = "https://files.pythonhosted.org/packages/2c/2b/0e92ad0ac446633f928a1563db4aa8add407e1924faf0ded5b95b35afb27/matplotlib-3.10.9-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:1872fb212a05b729e649754a72d5da61d03e0554d76e80303b6f83d1d2c0552b", size = 8293058 }, + { url = "https://files.pythonhosted.org/packages/4b/23/74682fd369f5299ceda438fea2a0662e6383b85c9383fb9cdfcf04713e07/matplotlib-3.10.9-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:985f2238880e2e69093f588f5fe2e46771747febf0649f3cf7f7b7480875317f", size = 8186627 }, + { url = "https://files.pythonhosted.org/packages/ca/e8/368aab88f3c4cd8992800f31abfe0670c3e47540ba20a97e9fdbcde594b3/matplotlib-3.10.9-pp310-pypy310_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:6640f75af2c6148293caa0a2b39dd806a492dd66c8a8b04035813e33d0fd2585", size = 8764117 }, + { url = "https://files.pythonhosted.org/packages/63/e2/9f66ca6a651a52abfe0d4964ce01439ed34f3f1e119de10ff3a07f403043/matplotlib-3.10.9-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:42fb814efabe95c06c1994d8ab5a8385f43a249e23badd3ba931d4308e5bca20", size = 8304420 }, + { url = "https://files.pythonhosted.org/packages/e8/e8/467c03568218792906aa87b5e7bb379b605e056ed0c74fe00c051786d925/matplotlib-3.10.9-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:f76e640a5268850bfda54b5131b1b1941cc685e42c5fa98ed9f2d64038308cba", size = 8197981 }, + { url = "https://files.pythonhosted.org/packages/6f/87/afead29192170917537934c6aff4b008c805fff7b1ccea0c79120d96beda/matplotlib-3.10.9-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3fc0364dfbe1d07f6d15c5ebd0c5bf89e126916e5a8667dd4a7a6e84c36653d4", size = 8774002 }, +] + +[[package]] +name = "matplotlib" +version = "3.11.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version == '3.12.*'", + "python_full_version == '3.11.*'", + "python_full_version >= '3.13'", +] +dependencies = [ + { name = "contourpy", version = "1.3.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "cycler", marker = "python_full_version >= '3.11'" }, + { name = "fonttools", marker = "python_full_version >= '3.11'" }, + { name = "kiwisolver", marker = "python_full_version >= '3.11'" }, + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.11.*'" }, + { name = "numpy", version = "2.5.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, + { name = "packaging", marker = "python_full_version >= '3.11'" }, + { name = "pillow", marker = "python_full_version >= '3.11'" }, + { name = "pyparsing", marker = "python_full_version >= '3.11'" }, + { name = "python-dateutil", marker = "python_full_version >= '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/1f/24/080c99d223d158d3a8902769269ab6da5b50f7a0e6e072513907e02b7a6c/matplotlib-3.11.0.tar.gz", hash = "sha256:68c0c7be01b30dcca3638934f7f591df73401235cbdbf0d1ab1c71e7db7f8b57", size = 33251176 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ce/a2/78f662f1b18968531f67d3fcde1b7ea8496920bacd4f16ddb5b79d112e46/matplotlib-3.11.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:f857524b442f0f36e641868ce2171aafa88cb0bc0644f4e1d8a5df9b32649fef", size = 9436261 }, + { url = "https://files.pythonhosted.org/packages/5e/92/044f1de43901310202f4c79acf4f141be53b2ca8d8380e2fcefb3d523a75/matplotlib-3.11.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:57baa92fdc82948ed716eae6d2579d4d6f40965cd8d2f416755b4a72580a3233", size = 9264669 }, + { url = "https://files.pythonhosted.org/packages/53/f4/f0b4f9ba7ec14a7af8151f3ad71ecfe3561e6ba38cfab1db3681ba4ca112/matplotlib-3.11.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:630eee0e67d35cce2019a0e670719f4816e3b86aff0fa72729f6c69786fceb45", size = 10021076 }, + { url = "https://files.pythonhosted.org/packages/d7/33/4d679c6dcd594a156542080ac907ddccf7b09ca11655c4b28eca8e9ee5da/matplotlib-3.11.0-cp311-cp311-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5106c444d0bf966eee2853548c03772af4ab7199118e086c62fbac8ccb07c055", size = 10828999 }, + { url = "https://files.pythonhosted.org/packages/07/74/0a3683802037d8cd013144d77c247219b47f2aabace6fdde74faa12bacf7/matplotlib-3.11.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:4d7aea652b58e686444079be3376ef546bffa1eee9b9bb9c472b9fcf6cf410d3", size = 10913103 }, + { url = "https://files.pythonhosted.org/packages/d0/9f/970fcbf381e82ec66fdf5da8ea76e2e9240f61a24011ce9fd1d42c37ac2d/matplotlib-3.11.0-cp311-cp311-win_amd64.whl", hash = "sha256:70a5b3e9a5dab708c0f039709ae7c68d5b4d254e291ef76492cdba230c8bb5e4", size = 9310945 }, + { url = "https://files.pythonhosted.org/packages/14/4e/6e7cfed23611265ded53806852343b5c59339e506e84c474a9b5afc3b249/matplotlib-3.11.0-cp311-cp311-win_arm64.whl", hash = "sha256:3d68266213e73823ac3be90615bab0cf31f88851e114cdb1dd25dacf3b01e1a7", size = 8999304 }, + { url = "https://files.pythonhosted.org/packages/da/17/f5276b496c61477a6c4fc5e7401f4bfe1c2e5ef7c6cd67896f2ade3809cb/matplotlib-3.11.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:06b5872e9cf11adc8f589ded3ce11bc3e1061ad498259664fabc1f6615beb918", size = 9449976 }, + { url = "https://files.pythonhosted.org/packages/82/34/bdd77418adb2178a1d59f044bd67bfebb115896e91b840b8a197eb3f4f4e/matplotlib-3.11.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0515d495124be3124340e59f164d901ed4484e2246a5b74cfa483cac3b80bd97", size = 9279307 }, + { url = "https://files.pythonhosted.org/packages/94/95/7f522393c88313336b20d70fc849555757b2e5febc22b83b3a3f0fd4bce9/matplotlib-3.11.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:be5f93a1d21981bfb802ded0d77a0caa92d4342a47d45754fac77e314a506344", size = 10031353 }, + { url = "https://files.pythonhosted.org/packages/87/ce/8f25a0e3186aefd61913e7467d1b999465bcd0d0c03ac695c1b26ca559b7/matplotlib-3.11.0-cp312-cp312-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:41635d7909d19e52e924a521dde6d8f670b0f53ab1d0e8c331fa831554f681d1", size = 10839232 }, + { url = "https://files.pythonhosted.org/packages/85/c2/db15da2bbdf9e3ca66df7db8e2c33a1dfed67be24a24d2c878efaaff01d6/matplotlib-3.11.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:94f5000f67ca9faa300863ea17f8bce9175cb67b88bec4bc7780502d53dd7c9e", size = 10923899 }, + { url = "https://files.pythonhosted.org/packages/e5/2f/a58a4443a4d052a4ea77557478336aefc26c7981f6408d37adba763aa758/matplotlib-3.11.0-cp312-cp312-win_amd64.whl", hash = "sha256:ac6f1ef39f3d0f9e2463303013094992cdbe0f85f43bc54155bc472b2042768e", size = 9329528 }, + { url = "https://files.pythonhosted.org/packages/61/0f/4b669589d47733b97ab9df4b58d6fc1e68acb5ea42a928dc7cbdd6bf5871/matplotlib-3.11.0-cp312-cp312-win_arm64.whl", hash = "sha256:9dd11fb612ce7bc60b1de5b4fc87ff959d22317b5de42aabf392f66f97af22eb", size = 9003413 }, + { url = "https://files.pythonhosted.org/packages/55/41/aa47f156b061d14c98b906f76c428507397708ec63ff94f410ae1752b426/matplotlib-3.11.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:6ce3b839b34ae1f430b4616893a2945a2999debaa7e94e7e29a2a8bbf286f7b5", size = 9450532 }, + { url = "https://files.pythonhosted.org/packages/8c/4f/5a9eb0375e81413953febf8af7b012a6b6357f53438a15c4f5ad86c6bbb5/matplotlib-3.11.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:373db8f91214e8ccaf35ac833cc1dd59dd961e148bbd55dd027141591dde1313", size = 9279760 }, + { url = "https://files.pythonhosted.org/packages/a4/c0/1117d53077e3ac3152503a84e9cf7a5c239576805ee71276e80c2aaa7471/matplotlib-3.11.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:be152b7570324dc8d01574cc9474dd2d803237acf528bcbb5b211fa347461a09", size = 10031623 }, + { url = "https://files.pythonhosted.org/packages/92/7e/e937138daffad65b71bf831a377809dcbc830fb4f31a31e067dc1faa2575/matplotlib-3.11.0-cp313-cp313-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:126f256df600652d7e4b394cf3164ff75210a00038f287c95a012a6f58d0e83f", size = 10839372 }, + { url = "https://files.pythonhosted.org/packages/1d/c2/438ecc197ffb8023b6b9922915542f2172f5fd45b76703b0b4fc47322243/matplotlib-3.11.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:03acfeddf87b0dddb11b081ef7740ad445a3ca8bcb6b8e3011b08f2cf802b75c", size = 10924099 }, + { url = "https://files.pythonhosted.org/packages/40/2e/395883da416f378b3ed2c9f3e843ac477eae1ce731b671b79adaa6f0bacd/matplotlib-3.11.0-cp313-cp313-win_amd64.whl", hash = "sha256:ab3722f04f3ff34c23b5012c5873d2894174e06c3822fcdac3610965a5ac7d06", size = 9329727 }, + { url = "https://files.pythonhosted.org/packages/61/82/2c388956abf8bf392dfb5b8917c502f1082df6a941b781ab8c8e5ba2474b/matplotlib-3.11.0-cp313-cp313-win_arm64.whl", hash = "sha256:c945824670fb8915b4ac879e5e61f3c58e0913022f70a0de4c082b17372f8771", size = 9003506 }, + { url = "https://files.pythonhosted.org/packages/c8/c1/34454baa44da7975ada82e9aea37105ec47059514dc967d3be14426ba8dc/matplotlib-3.11.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:3489c3dc487669b4a980bc3068f87856de7a1564248d3f6c629efb2a58b03f24", size = 9499838 }, + { url = "https://files.pythonhosted.org/packages/b1/c3/98fe79a398cf232219f090163a7fa7e6766e9f2e0ad26df54d6f8934d8ee/matplotlib-3.11.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:6a98f5476ce784a50ce09998f4ae1e6a9f25043cef8a480c98949902eda74620", size = 9332298 }, + { url = "https://files.pythonhosted.org/packages/95/e4/b4b7c33151e74e5c802f3cde1ba807ebfc38401e329b44e215a5888dd76d/matplotlib-3.11.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:565af866fd63e4bd3f987d580afe27c44c2552a3b3305f4ecbb85133601ea6f3", size = 10045491 }, + { url = "https://files.pythonhosted.org/packages/71/28/394548efd68354110c1a1be11fe6b6e559e06d1a23da35908a0e316c55a9/matplotlib-3.11.0-cp313-cp313t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e6b3e64dea5062c570f04358e2711859f3531b459f29516274fbad889079e4f3", size = 10857059 }, + { url = "https://files.pythonhosted.org/packages/c8/44/e7922e6e2a4d63bdfbc9dc4a53e3850ab438d46cf42e6779bb15ec92c948/matplotlib-3.11.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:942b37c5db1899610bd1543ce8e13e4ecff9a4633e7f63bb6aa9205d2644ebd1", size = 10939576 }, + { url = "https://files.pythonhosted.org/packages/3d/be/b1ca96003a441d619b727fee21d671fdff7a5ce2f1bb797b2521aa2f679a/matplotlib-3.11.0-cp313-cp313t-win_amd64.whl", hash = "sha256:c08e649a6313e1291e713623b97a38e5bb4aa580b2a100a94a3309bc6b9c8eb3", size = 9379519 }, + { url = "https://files.pythonhosted.org/packages/e3/72/4bf3b91821c34596dd6a7bdac5836d94f744144c8208939ef49d8ec43f7e/matplotlib-3.11.0-cp313-cp313t-win_arm64.whl", hash = "sha256:2746cd2c113742ff6ce37a864c5ac5fd7aa644568f445e66166e457ac78e40e0", size = 9055456 }, + { url = "https://files.pythonhosted.org/packages/57/52/a94102ac99eb78e2fe9b826674f9ef9ee23327110ea6ab4776c1b4eb6209/matplotlib-3.11.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:3338e3e3de128cf50d0d2fb92a122815daf9c755bd882a474343c05f8fd7ec79", size = 9452137 }, + { url = "https://files.pythonhosted.org/packages/7c/03/b8cdb625a21f710dfa11bbca1f48fb4057d2c0286975f8b415bf80942c99/matplotlib-3.11.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:25c2e5455efd8d99f41fb79871a31feb7d301569642e332ec58d72cfe9282bc3", size = 9281514 }, + { url = "https://files.pythonhosted.org/packages/b7/2d/4e1240ea82ee197dfb3851e71f71c87eeeb975f1753b56a0588e4e80739a/matplotlib-3.11.0-cp314-cp314-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d9695457a467ff86d23f35037a43deb6f1134dd6d3e2ac8ce1e2087cff09ffb9", size = 10843005 }, + { url = "https://files.pythonhosted.org/packages/29/dc/6377ecfaa5fef79430f74a1a16638b4e2aa30d4692bae2c19f9d76fe3b01/matplotlib-3.11.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:19c16c61dea63b3582918503e6b294193961261d9daa806d4ae2151f1ad05430", size = 11127459 }, + { url = "https://files.pythonhosted.org/packages/6f/41/795c405aa7560443a3b01309424cde4a1113b85c90b8a63417444a749617/matplotlib-3.11.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2d72ea8b7924f3cb955e61518d21e43b3df1e6c8a793b480a0c1214f185d30ba", size = 10925160 }, + { url = "https://files.pythonhosted.org/packages/1a/f7/3a9e6389a7cfaeff76c56e40c2dabcb13110e21e82f837228c834ebe748c/matplotlib-3.11.0-cp314-cp314-win_amd64.whl", hash = "sha256:1c02da0a629dfa9debf52725ea06866b74c1fb70a895bae05e4493d34074f9f2", size = 9485186 }, + { url = "https://files.pythonhosted.org/packages/8b/c0/396478ee7cf2091d182db8b4a8695f6a37f1ddb978989cf9dbb84cd5c123/matplotlib-3.11.0-cp314-cp314-win_arm64.whl", hash = "sha256:aa55d73b3117d4b07f959cd9eb6f69b375d8df3414139c479388e551aa5d999d", size = 9160349 }, + { url = "https://files.pythonhosted.org/packages/c5/6f/1c3bd51bb2b34eaacdcf3c3d859dbb357f952fc8020c617dc118ad7c9e38/matplotlib-3.11.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:a9d8c6e7cd2f0ddf11d8d92e520dd1d9d2abb0cf6ac8831e338666c81e905847", size = 9500921 }, + { url = "https://files.pythonhosted.org/packages/e0/0d/4d861d0121840cb1a3fd4a10deb211efd6fccd481ed23e553f31f4f4da4a/matplotlib-3.11.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:be050fcf32f729eda99f7f75a80bf67612ce16ab9ac1c23a387dcaede95cb70e", size = 9332190 }, + { url = "https://files.pythonhosted.org/packages/4b/cb/22f6bc35711a0b5639a784e74e653e77c86210bd4304449dd399a482f74e/matplotlib-3.11.0-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:dfabef0230d0697aa0d717385194dd41162e00207a68bf4abf94c2bf4c27dca0", size = 10854181 }, + { url = "https://files.pythonhosted.org/packages/3f/7e/9a9eaca731a2939589da520f0ebe8fd8753d0f51fca98c7d20af6dbe261a/matplotlib-3.11.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1644db30e759199443493ac5e5caec24fdb775a8f6123021f85ba47c4133c3cb", size = 11137715 }, + { url = "https://files.pythonhosted.org/packages/ef/f9/9b030b6088354acb0296871bb624b25befc1c42509d3c6cd17420c83a5b8/matplotlib-3.11.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:15b0d160079cb10699a0e98b5989c70677b2df7cacdc62af67c30f2facec46d9", size = 10939427 }, + { url = "https://files.pythonhosted.org/packages/59/94/6b273eaee4ee250863567d100865da61a5c1527fa67f527b7ed22e0dd29c/matplotlib-3.11.0-cp314-cp314t-win_amd64.whl", hash = "sha256:446307e6b04b57b1f1239e228a1ec2af0d589a1008cebc3dfa3f5441d095cfb6", size = 9535809 }, + { url = "https://files.pythonhosted.org/packages/60/95/1d36bddf2b7e2692c1540e78a6e5bc88bc1496b137e3e35a611f91b65ac3/matplotlib-3.11.0-cp314-cp314t-win_arm64.whl", hash = "sha256:652fb5696271d4c50f196d22a5ff4f8e4444c74f847423570d7dc0aa2bbd0159", size = 9209226 }, + { url = "https://files.pythonhosted.org/packages/0f/c2/f5da6cd37ed6871f5c9b3c0507ddb69f14d6c36fac4541e4e0c60cb8cdfc/matplotlib-3.11.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:81ae77077a1e16d37a5b61096ccb07c8d90a99b518fa8256b8f21578932f2f62", size = 9434094 }, + { url = "https://files.pythonhosted.org/packages/f8/07/56f66906e0f87a0c6d0d0acbd34dbc9432b1931d8f26ef618bd6f92932a9/matplotlib-3.11.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:ddef37840695f5eef65f9f070fe2d2f510f584c2156203f9f622a5b0584efffd", size = 9262183 }, + { url = "https://files.pythonhosted.org/packages/0c/d8/c4ecab06b7ea36a570c4f3bd2d48d1799fd5d9174470e45c2194199431e7/matplotlib-3.11.0-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:cf662e5ac5707658cb931e19972c4bd99f7b4f8b7bf79d3c821d239fa6b71e64", size = 10015653 }, +] + +[[package]] +name = "matplotlib-inline" +version = "0.2.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "traitlets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/bd/c0/9f7c9a46090390368a4d7bcb76bb87a4a36c421e4c0792cdb53486ffac7a/matplotlib_inline-0.2.2.tar.gz", hash = "sha256:72f3fe8fce36b70d4a5b612f899090cd0401deddc4ea90e1572b9f4bfb058c79", size = 8150 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/41/09/5b161152e2d90f7b87f781c2e1267494aef9c32498df793f73ad0a0a494a/matplotlib_inline-0.2.2-py3-none-any.whl", hash = "sha256:3c821cf1c209f59fb2d2d64abbf5b23b67bcb2210d663f9918dd851c6da1fcf6", size = 9534 }, +] + +[[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 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979 }, +] + +[[package]] +name = "numpy" +version = "2.2.6" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.11'", +] +sdist = { url = "https://files.pythonhosted.org/packages/76/21/7d2a95e4bba9dc13d043ee156a356c0a8f0c6309dff6b21b4d71a073b8a8/numpy-2.2.6.tar.gz", hash = "sha256:e29554e2bef54a90aa5cc07da6ce955accb83f21ab5de01a62c8478897b264fd", size = 20276440 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9a/3e/ed6db5be21ce87955c0cbd3009f2803f59fa08df21b5df06862e2d8e2bdd/numpy-2.2.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b412caa66f72040e6d268491a59f2c43bf03eb6c96dd8f0307829feb7fa2b6fb", size = 21165245 }, + { url = "https://files.pythonhosted.org/packages/22/c2/4b9221495b2a132cc9d2eb862e21d42a009f5a60e45fc44b00118c174bff/numpy-2.2.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8e41fd67c52b86603a91c1a505ebaef50b3314de0213461c7a6e99c9a3beff90", size = 14360048 }, + { url = "https://files.pythonhosted.org/packages/fd/77/dc2fcfc66943c6410e2bf598062f5959372735ffda175b39906d54f02349/numpy-2.2.6-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:37e990a01ae6ec7fe7fa1c26c55ecb672dd98b19c3d0e1d1f326fa13cb38d163", size = 5340542 }, + { url = "https://files.pythonhosted.org/packages/7a/4f/1cb5fdc353a5f5cc7feb692db9b8ec2c3d6405453f982435efc52561df58/numpy-2.2.6-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:5a6429d4be8ca66d889b7cf70f536a397dc45ba6faeb5f8c5427935d9592e9cf", size = 6878301 }, + { url = "https://files.pythonhosted.org/packages/eb/17/96a3acd228cec142fcb8723bd3cc39c2a474f7dcf0a5d16731980bcafa95/numpy-2.2.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:efd28d4e9cd7d7a8d39074a4d44c63eda73401580c5c76acda2ce969e0a38e83", size = 14297320 }, + { url = "https://files.pythonhosted.org/packages/b4/63/3de6a34ad7ad6646ac7d2f55ebc6ad439dbbf9c4370017c50cf403fb19b5/numpy-2.2.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fc7b73d02efb0e18c000e9ad8b83480dfcd5dfd11065997ed4c6747470ae8915", size = 16801050 }, + { url = "https://files.pythonhosted.org/packages/07/b6/89d837eddef52b3d0cec5c6ba0456c1bf1b9ef6a6672fc2b7873c3ec4e2e/numpy-2.2.6-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:74d4531beb257d2c3f4b261bfb0fc09e0f9ebb8842d82a7b4209415896adc680", size = 15807034 }, + { url = "https://files.pythonhosted.org/packages/01/c8/dc6ae86e3c61cfec1f178e5c9f7858584049b6093f843bca541f94120920/numpy-2.2.6-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:8fc377d995680230e83241d8a96def29f204b5782f371c532579b4f20607a289", size = 18614185 }, + { url = "https://files.pythonhosted.org/packages/5b/c5/0064b1b7e7c89137b471ccec1fd2282fceaae0ab3a9550f2568782d80357/numpy-2.2.6-cp310-cp310-win32.whl", hash = "sha256:b093dd74e50a8cba3e873868d9e93a85b78e0daf2e98c6797566ad8044e8363d", size = 6527149 }, + { url = "https://files.pythonhosted.org/packages/a3/dd/4b822569d6b96c39d1215dbae0582fd99954dcbcf0c1a13c61783feaca3f/numpy-2.2.6-cp310-cp310-win_amd64.whl", hash = "sha256:f0fd6321b839904e15c46e0d257fdd101dd7f530fe03fd6359c1ea63738703f3", size = 12904620 }, + { url = "https://files.pythonhosted.org/packages/da/a8/4f83e2aa666a9fbf56d6118faaaf5f1974d456b1823fda0a176eff722839/numpy-2.2.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f9f1adb22318e121c5c69a09142811a201ef17ab257a1e66ca3025065b7f53ae", size = 21176963 }, + { url = "https://files.pythonhosted.org/packages/b3/2b/64e1affc7972decb74c9e29e5649fac940514910960ba25cd9af4488b66c/numpy-2.2.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c820a93b0255bc360f53eca31a0e676fd1101f673dda8da93454a12e23fc5f7a", size = 14406743 }, + { url = "https://files.pythonhosted.org/packages/4a/9f/0121e375000b5e50ffdd8b25bf78d8e1a5aa4cca3f185d41265198c7b834/numpy-2.2.6-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:3d70692235e759f260c3d837193090014aebdf026dfd167834bcba43e30c2a42", size = 5352616 }, + { url = "https://files.pythonhosted.org/packages/31/0d/b48c405c91693635fbe2dcd7bc84a33a602add5f63286e024d3b6741411c/numpy-2.2.6-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:481b49095335f8eed42e39e8041327c05b0f6f4780488f61286ed3c01368d491", size = 6889579 }, + { url = "https://files.pythonhosted.org/packages/52/b8/7f0554d49b565d0171eab6e99001846882000883998e7b7d9f0d98b1f934/numpy-2.2.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b64d8d4d17135e00c8e346e0a738deb17e754230d7e0810ac5012750bbd85a5a", size = 14312005 }, + { url = "https://files.pythonhosted.org/packages/b3/dd/2238b898e51bd6d389b7389ffb20d7f4c10066d80351187ec8e303a5a475/numpy-2.2.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba10f8411898fc418a521833e014a77d3ca01c15b0c6cdcce6a0d2897e6dbbdf", size = 16821570 }, + { url = "https://files.pythonhosted.org/packages/83/6c/44d0325722cf644f191042bf47eedad61c1e6df2432ed65cbe28509d404e/numpy-2.2.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:bd48227a919f1bafbdda0583705e547892342c26fb127219d60a5c36882609d1", size = 15818548 }, + { url = "https://files.pythonhosted.org/packages/ae/9d/81e8216030ce66be25279098789b665d49ff19eef08bfa8cb96d4957f422/numpy-2.2.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:9551a499bf125c1d4f9e250377c1ee2eddd02e01eac6644c080162c0c51778ab", size = 18620521 }, + { url = "https://files.pythonhosted.org/packages/6a/fd/e19617b9530b031db51b0926eed5345ce8ddc669bb3bc0044b23e275ebe8/numpy-2.2.6-cp311-cp311-win32.whl", hash = "sha256:0678000bb9ac1475cd454c6b8c799206af8107e310843532b04d49649c717a47", size = 6525866 }, + { url = "https://files.pythonhosted.org/packages/31/0a/f354fb7176b81747d870f7991dc763e157a934c717b67b58456bc63da3df/numpy-2.2.6-cp311-cp311-win_amd64.whl", hash = "sha256:e8213002e427c69c45a52bbd94163084025f533a55a59d6f9c5b820774ef3303", size = 12907455 }, + { url = "https://files.pythonhosted.org/packages/82/5d/c00588b6cf18e1da539b45d3598d3557084990dcc4331960c15ee776ee41/numpy-2.2.6-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:41c5a21f4a04fa86436124d388f6ed60a9343a6f767fced1a8a71c3fbca038ff", size = 20875348 }, + { url = "https://files.pythonhosted.org/packages/66/ee/560deadcdde6c2f90200450d5938f63a34b37e27ebff162810f716f6a230/numpy-2.2.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:de749064336d37e340f640b05f24e9e3dd678c57318c7289d222a8a2f543e90c", size = 14119362 }, + { url = "https://files.pythonhosted.org/packages/3c/65/4baa99f1c53b30adf0acd9a5519078871ddde8d2339dc5a7fde80d9d87da/numpy-2.2.6-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:894b3a42502226a1cac872f840030665f33326fc3dac8e57c607905773cdcde3", size = 5084103 }, + { url = "https://files.pythonhosted.org/packages/cc/89/e5a34c071a0570cc40c9a54eb472d113eea6d002e9ae12bb3a8407fb912e/numpy-2.2.6-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:71594f7c51a18e728451bb50cc60a3ce4e6538822731b2933209a1f3614e9282", size = 6625382 }, + { url = "https://files.pythonhosted.org/packages/f8/35/8c80729f1ff76b3921d5c9487c7ac3de9b2a103b1cd05e905b3090513510/numpy-2.2.6-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f2618db89be1b4e05f7a1a847a9c1c0abd63e63a1607d892dd54668dd92faf87", size = 14018462 }, + { url = "https://files.pythonhosted.org/packages/8c/3d/1e1db36cfd41f895d266b103df00ca5b3cbe965184df824dec5c08c6b803/numpy-2.2.6-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd83c01228a688733f1ded5201c678f0c53ecc1006ffbc404db9f7a899ac6249", size = 16527618 }, + { url = "https://files.pythonhosted.org/packages/61/c6/03ed30992602c85aa3cd95b9070a514f8b3c33e31124694438d88809ae36/numpy-2.2.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:37c0ca431f82cd5fa716eca9506aefcabc247fb27ba69c5062a6d3ade8cf8f49", size = 15505511 }, + { url = "https://files.pythonhosted.org/packages/b7/25/5761d832a81df431e260719ec45de696414266613c9ee268394dd5ad8236/numpy-2.2.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fe27749d33bb772c80dcd84ae7e8df2adc920ae8297400dabec45f0dedb3f6de", size = 18313783 }, + { url = "https://files.pythonhosted.org/packages/57/0a/72d5a3527c5ebffcd47bde9162c39fae1f90138c961e5296491ce778e682/numpy-2.2.6-cp312-cp312-win32.whl", hash = "sha256:4eeaae00d789f66c7a25ac5f34b71a7035bb474e679f410e5e1a94deb24cf2d4", size = 6246506 }, + { url = "https://files.pythonhosted.org/packages/36/fa/8c9210162ca1b88529ab76b41ba02d433fd54fecaf6feb70ef9f124683f1/numpy-2.2.6-cp312-cp312-win_amd64.whl", hash = "sha256:c1f9540be57940698ed329904db803cf7a402f3fc200bfe599334c9bd84a40b2", size = 12614190 }, + { url = "https://files.pythonhosted.org/packages/f9/5c/6657823f4f594f72b5471f1db1ab12e26e890bb2e41897522d134d2a3e81/numpy-2.2.6-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0811bb762109d9708cca4d0b13c4f67146e3c3b7cf8d34018c722adb2d957c84", size = 20867828 }, + { url = "https://files.pythonhosted.org/packages/dc/9e/14520dc3dadf3c803473bd07e9b2bd1b69bc583cb2497b47000fed2fa92f/numpy-2.2.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:287cc3162b6f01463ccd86be154f284d0893d2b3ed7292439ea97eafa8170e0b", size = 14143006 }, + { url = "https://files.pythonhosted.org/packages/4f/06/7e96c57d90bebdce9918412087fc22ca9851cceaf5567a45c1f404480e9e/numpy-2.2.6-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:f1372f041402e37e5e633e586f62aa53de2eac8d98cbfb822806ce4bbefcb74d", size = 5076765 }, + { url = "https://files.pythonhosted.org/packages/73/ed/63d920c23b4289fdac96ddbdd6132e9427790977d5457cd132f18e76eae0/numpy-2.2.6-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:55a4d33fa519660d69614a9fad433be87e5252f4b03850642f88993f7b2ca566", size = 6617736 }, + { url = "https://files.pythonhosted.org/packages/85/c5/e19c8f99d83fd377ec8c7e0cf627a8049746da54afc24ef0a0cb73d5dfb5/numpy-2.2.6-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f92729c95468a2f4f15e9bb94c432a9229d0d50de67304399627a943201baa2f", size = 14010719 }, + { url = "https://files.pythonhosted.org/packages/19/49/4df9123aafa7b539317bf6d342cb6d227e49f7a35b99c287a6109b13dd93/numpy-2.2.6-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1bc23a79bfabc5d056d106f9befb8d50c31ced2fbc70eedb8155aec74a45798f", size = 16526072 }, + { url = "https://files.pythonhosted.org/packages/b2/6c/04b5f47f4f32f7c2b0e7260442a8cbcf8168b0e1a41ff1495da42f42a14f/numpy-2.2.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e3143e4451880bed956e706a3220b4e5cf6172ef05fcc397f6f36a550b1dd868", size = 15503213 }, + { url = "https://files.pythonhosted.org/packages/17/0a/5cd92e352c1307640d5b6fec1b2ffb06cd0dabe7d7b8227f97933d378422/numpy-2.2.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b4f13750ce79751586ae2eb824ba7e1e8dba64784086c98cdbbcc6a42112ce0d", size = 18316632 }, + { url = "https://files.pythonhosted.org/packages/f0/3b/5cba2b1d88760ef86596ad0f3d484b1cbff7c115ae2429678465057c5155/numpy-2.2.6-cp313-cp313-win32.whl", hash = "sha256:5beb72339d9d4fa36522fc63802f469b13cdbe4fdab4a288f0c441b74272ebfd", size = 6244532 }, + { url = "https://files.pythonhosted.org/packages/cb/3b/d58c12eafcb298d4e6d0d40216866ab15f59e55d148a5658bb3132311fcf/numpy-2.2.6-cp313-cp313-win_amd64.whl", hash = "sha256:b0544343a702fa80c95ad5d3d608ea3599dd54d4632df855e4c8d24eb6ecfa1c", size = 12610885 }, + { url = "https://files.pythonhosted.org/packages/6b/9e/4bf918b818e516322db999ac25d00c75788ddfd2d2ade4fa66f1f38097e1/numpy-2.2.6-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0bca768cd85ae743b2affdc762d617eddf3bcf8724435498a1e80132d04879e6", size = 20963467 }, + { url = "https://files.pythonhosted.org/packages/61/66/d2de6b291507517ff2e438e13ff7b1e2cdbdb7cb40b3ed475377aece69f9/numpy-2.2.6-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:fc0c5673685c508a142ca65209b4e79ed6740a4ed6b2267dbba90f34b0b3cfda", size = 14225144 }, + { url = "https://files.pythonhosted.org/packages/e4/25/480387655407ead912e28ba3a820bc69af9adf13bcbe40b299d454ec011f/numpy-2.2.6-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:5bd4fc3ac8926b3819797a7c0e2631eb889b4118a9898c84f585a54d475b7e40", size = 5200217 }, + { url = "https://files.pythonhosted.org/packages/aa/4a/6e313b5108f53dcbf3aca0c0f3e9c92f4c10ce57a0a721851f9785872895/numpy-2.2.6-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:fee4236c876c4e8369388054d02d0e9bb84821feb1a64dd59e137e6511a551f8", size = 6712014 }, + { url = "https://files.pythonhosted.org/packages/b7/30/172c2d5c4be71fdf476e9de553443cf8e25feddbe185e0bd88b096915bcc/numpy-2.2.6-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e1dda9c7e08dc141e0247a5b8f49cf05984955246a327d4c48bda16821947b2f", size = 14077935 }, + { url = "https://files.pythonhosted.org/packages/12/fb/9e743f8d4e4d3c710902cf87af3512082ae3d43b945d5d16563f26ec251d/numpy-2.2.6-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f447e6acb680fd307f40d3da4852208af94afdfab89cf850986c3ca00562f4fa", size = 16600122 }, + { url = "https://files.pythonhosted.org/packages/12/75/ee20da0e58d3a66f204f38916757e01e33a9737d0b22373b3eb5a27358f9/numpy-2.2.6-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:389d771b1623ec92636b0786bc4ae56abafad4a4c513d36a55dce14bd9ce8571", size = 15586143 }, + { url = "https://files.pythonhosted.org/packages/76/95/bef5b37f29fc5e739947e9ce5179ad402875633308504a52d188302319c8/numpy-2.2.6-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8e9ace4a37db23421249ed236fdcdd457d671e25146786dfc96835cd951aa7c1", size = 18385260 }, + { url = "https://files.pythonhosted.org/packages/09/04/f2f83279d287407cf36a7a8053a5abe7be3622a4363337338f2585e4afda/numpy-2.2.6-cp313-cp313t-win32.whl", hash = "sha256:038613e9fb8c72b0a41f025a7e4c3f0b7a1b5d768ece4796b674c8f3fe13efff", size = 6377225 }, + { url = "https://files.pythonhosted.org/packages/67/0e/35082d13c09c02c011cf21570543d202ad929d961c02a147493cb0c2bdf5/numpy-2.2.6-cp313-cp313t-win_amd64.whl", hash = "sha256:6031dd6dfecc0cf9f668681a37648373bddd6421fff6c66ec1624eed0180ee06", size = 12771374 }, + { url = "https://files.pythonhosted.org/packages/9e/3b/d94a75f4dbf1ef5d321523ecac21ef23a3cd2ac8b78ae2aac40873590229/numpy-2.2.6-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:0b605b275d7bd0c640cad4e5d30fa701a8d59302e127e5f79138ad62762c3e3d", size = 21040391 }, + { url = "https://files.pythonhosted.org/packages/17/f4/09b2fa1b58f0fb4f7c7963a1649c64c4d315752240377ed74d9cd878f7b5/numpy-2.2.6-pp310-pypy310_pp73-macosx_14_0_x86_64.whl", hash = "sha256:7befc596a7dc9da8a337f79802ee8adb30a552a94f792b9c9d18c840055907db", size = 6786754 }, + { url = "https://files.pythonhosted.org/packages/af/30/feba75f143bdc868a1cc3f44ccfa6c4b9ec522b36458e738cd00f67b573f/numpy-2.2.6-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ce47521a4754c8f4593837384bd3424880629f718d87c5d44f8ed763edd63543", size = 16643476 }, + { url = "https://files.pythonhosted.org/packages/37/48/ac2a9584402fb6c0cd5b5d1a91dcf176b15760130dd386bbafdbfe3640bf/numpy-2.2.6-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:d042d24c90c41b54fd506da306759e06e568864df8ec17ccc17e9e884634fd00", size = 12812666 }, +] + +[[package]] +name = "numpy" +version = "2.4.6" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version == '3.11.*'", +] +sdist = { url = "https://files.pythonhosted.org/packages/d0/ad/fed0499ce6a338d2a03ebae59cd15093910c8875328855781952abf6c2fe/numpy-2.4.6.tar.gz", hash = "sha256:f3a3570c4a2a16746ac2c31a7c7c7b0c186b95ce902e33db6f28094ed7387dda", size = 20735807 } +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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { url = "https://files.pythonhosted.org/packages/15/a7/9bc1cd626d7bf6869bfedf27b91b6ab5dd607758bf8e959d6fa80c6a59cb/numpy-2.4.6-cp311-cp311-win32.whl", hash = "sha256:ddea102b48f9e339f3948bf22040944184627a30fdf7f858667673b9c5f033c8", size = 6233945 }, + { url = "https://files.pythonhosted.org/packages/c5/31/7fc6239c12bce7e931463251cca4426c465e1876ba3cc785402ef4dd8f4e/numpy-2.4.6-cp311-cp311-win_amd64.whl", hash = "sha256:1e254a00cdf42b1e4d5b3d68d33af63268d41340d8885df2ab6470f2e1500147", size = 12608406 }, + { url = "https://files.pythonhosted.org/packages/27/83/140f85a466595a16382996a1bf06b2b54bcd597488921b0c9daaeeda72af/numpy-2.4.6-cp311-cp311-win_arm64.whl", hash = "sha256:ed9749eef4cbd126da3dc1d6bcb3a57f5eb7ac6a6484146bdbf743f552dfc577", size = 10479528 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { url = "https://files.pythonhosted.org/packages/f6/81/e1b27545deedce7f4a0b348618c6b62d74e36a4dc9ccd42f3eb2f85eee32/numpy-2.4.6-cp312-cp312-win32.whl", hash = "sha256:e3eeb0aabd6bd5ce64faae67e9935203a6991b4bc2a485a767fbafb2c5125f45", size = 5962825 }, + { url = "https://files.pythonhosted.org/packages/ab/ca/feab00bd44aa5fe1ad2c18f08b4d3bb92e26484b0b1d1443897809ed528c/numpy-2.4.6-cp312-cp312-win_amd64.whl", hash = "sha256:d8e8286dd7cea7895157318d1b91cdacac64c479f3cbc8dce548331728484751", size = 12321687 }, + { url = "https://files.pythonhosted.org/packages/63/cf/5a6d34850a39d1093558564f77ee8e8e0bee5061151b8f05a55711001ec7/numpy-2.4.6-cp312-cp312-win_arm64.whl", hash = "sha256:4081eb135ac24158bd51cdfbef16f1c64df7063b1143f24731387137c092bec8", size = 10221482 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { url = "https://files.pythonhosted.org/packages/f7/da/2ccc6c2fe8898dee01d90c75c5f5f914a23daf99e3e0f59516a08760c8b5/numpy-2.4.6-cp313-cp313-win32.whl", hash = "sha256:56b39e5e0622a09a25bf5baf62f4bcf0cb8a41ae6e2819cf49bbc5a74c083f91", size = 5961134 }, + { url = "https://files.pythonhosted.org/packages/b5/cd/9cc4dc876fb065d5c220aae4d5e14826b2715331bb7618ce1fb07a679d99/numpy-2.4.6-cp313-cp313-win_amd64.whl", hash = "sha256:c4fc99836233ea196540b17ab0983aff60ed07941751930f5f4d05bc3b3b7359", size = 12318598 }, + { url = "https://files.pythonhosted.org/packages/39/1e/c0bcba1f8694116485fe28fd1be698c278fcda4141c5b0e53a2aed8b12a8/numpy-2.4.6-cp313-cp313-win_arm64.whl", hash = "sha256:a7c711e21628b52034bb5ab8d1bce291f752fcc5e92accc615778acee1ff4778", size = 10222272 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { url = "https://files.pythonhosted.org/packages/8a/90/0ac3bc947217e66dec77e7cbc6a1979d1af70b6461b82f620d3bccd5e4c8/numpy-2.4.6-cp313-cp313t-win32.whl", hash = "sha256:29a287e0cf63ff528da061de6b9f64a4618da591ca1046aafc54062e40ca7eab", size = 6084971 }, + { url = "https://files.pythonhosted.org/packages/77/71/5673e351671a1d2bd6063b91b44f70c0affea7d1516fa7a6572941ba4aa1/numpy-2.4.6-cp313-cp313t-win_amd64.whl", hash = "sha256:25c692919ac5a01f170a3bfcd62d745b24fd095c353d50812637d6fcab442e75", size = 12458532 }, + { url = "https://files.pythonhosted.org/packages/3f/88/19d3503c5046e688f049274b27a3ef3d771152fa80d3ba3d01a3dff61abe/numpy-2.4.6-cp313-cp313t-win_arm64.whl", hash = "sha256:1e978ec1e8bd0e0e4de6bb75de9d30cbb74db6b6a2bb727618613703ca0167dd", size = 10291881 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { url = "https://files.pythonhosted.org/packages/aa/b4/298628d98c72b57e57f7165ae6a481a1deaf6f3c28262a6e4c739c275930/numpy-2.4.6-cp314-cp314-win32.whl", hash = "sha256:aaf159caa35993cb1f56fb9b8e4610d35758e7ca005412eb1daa856a78c9c4b1", size = 6010196 }, + { url = "https://files.pythonhosted.org/packages/df/ac/46de6dda46478f7942f839e094970be2d4a861e005c4b3bf07c92e291a09/numpy-2.4.6-cp314-cp314-win_amd64.whl", hash = "sha256:b507f5c4c1d508876d1819b6bf9a49d365b96320b5d4993426b33a23ca4b8261", size = 12450334 }, + { url = "https://files.pythonhosted.org/packages/78/92/b8b798ac784102c0da830d2257d59358e3d3d90d1e2b3f2575dad976c5cf/numpy-2.4.6-cp314-cp314-win_arm64.whl", hash = "sha256:6f41ae150c4e32db4f3310cdaf64b1593a03dbabe29eec77fc9b50fe64061df6", size = 10495678 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { url = "https://files.pythonhosted.org/packages/9c/50/0753655aa844c99cd9e018aacf76f130f1bd81d881bb74bc0aef5d73a8ba/numpy-2.4.6-cp314-cp314t-win32.whl", hash = "sha256:260a5d70215b61ab4fadf5c7baacd64821842975eea312125ed3c39a6391b063", size = 6156982 }, + { url = "https://files.pythonhosted.org/packages/b2/d4/7c67becf668f973cb490cec3e98dfd799d866f9c989a54d355672cfa0db6/numpy-2.4.6-cp314-cp314t-win_amd64.whl", hash = "sha256:81a1cca95ed5bb92aa8b10dd2cdc9a0d3853a50fad926c28b5d7e8ea54389627", size = 12638908 }, + { url = "https://files.pythonhosted.org/packages/43/bb/e1c71a4295b1b1d1393d50dbb4f2a36283c6859d9d3892e84f00ec5a91d5/numpy-2.4.6-cp314-cp314t-win_arm64.whl", hash = "sha256:0c9136e14ed34a9e343a31c533d78a9813a69a3148332bce5e9821cb2f996e66", size = 10565867 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { url = "https://files.pythonhosted.org/packages/15/ce/e5ec180bc41812edcd8daeb8639d205622c0e8c02259d8ab25a0201b3c2a/numpy-2.4.6-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:2803abfebfc990042cd494d8ce2d5f82e9d847af6d35ec486923aa19dbad5e73", size = 12504263 }, +] + +[[package]] +name = "numpy" +version = "2.5.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version == '3.12.*'", + "python_full_version >= '3.13'", +] +sdist = { url = "https://files.pythonhosted.org/packages/e7/05/3d27272d30698dc0ecb7fdfaa41ad70303b444f81722bb99bce1d818638a/numpy-2.5.0.tar.gz", hash = "sha256:5a129578019311b6e56bdd714250f19b518f7dceeeb8d1af5490f4942d3f891c", size = 20652461 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fa/0a/11486d02add7b1384dff7374d124b1cfbb0ee864dcc9f6a2c0380638cf84/numpy-2.5.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:489780423903667933b4ed6197b6ec3b75ea5dd17d1d8f0f38d798feb6921561", size = 16789987 }, + { url = "https://files.pythonhosted.org/packages/55/b2/285f48640a181947b4587a3766d21ec1eaa7fea833d4b49957e09da467a2/numpy-2.5.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ece55976ced6bca95a03ae2839e2e5ccffe8eb6a3e7022415645eb154a81e4e6", size = 11760322 }, + { url = "https://files.pythonhosted.org/packages/dd/67/b032db1eb03ca30d16eda3b0c22aaa615338b9263c2fd559d0f29451aca4/numpy-2.5.0-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:c83b664b0e6eee9594fa920cf0639d8af796606d3fad6cc70180c87e4b97c7be", size = 5319605 }, + { url = "https://files.pythonhosted.org/packages/b9/83/03fc7300c7c6b6c84c487b1dc80d322817b95fbd1f4dd57a85e23b7198de/numpy-2.5.0-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:bf80333980bf37f523341ddd72c783f39d6829ec7736b9eb99086388a2d52cc2", size = 6653628 }, + { url = "https://files.pythonhosted.org/packages/82/49/2ec21730bc63ccfda829323f7040a8ed4715b3852ce658689cf74ee96a8c/numpy-2.5.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a1a4874217b36d5ac8fc876f52e39df56f8182c88463e9e2dceabf7ca8b7efb8", size = 15153691 }, + { url = "https://files.pythonhosted.org/packages/bb/6b/f4a3d0637692c49da8ef99d72d52526f92e0a8d6ac4f0ca9f31441b9d9ea/numpy-2.5.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:aaa760137137e8d3c920d27927748215b56014f92667dc9b6c27dfc61249255a", size = 16660066 }, + { url = "https://files.pythonhosted.org/packages/3a/2f/c354ec86d1f3f5c19649463b0d39652e160736e5b0a4cd18dff0576715c4/numpy-2.5.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:7174ce8265fc7f7417d171c9ea8fe905220748893ea67a2a7abe726ec331c4b0", size = 16514638 }, + { url = "https://files.pythonhosted.org/packages/06/34/43efdcb319988648580f93c11f1ae82cf7e2faa74925e98e454ae3aa95f8/numpy-2.5.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b8c3daaf99de52415d20b42f8e8155c78642cb04207d02f9d317a0dcf1b3fb54", size = 18419647 }, + { url = "https://files.pythonhosted.org/packages/71/e2/f5d1676b1d7fb682eb5e9a1641e7ebd2414b3216c370661d1029778908b4/numpy-2.5.0-cp312-cp312-win32.whl", hash = "sha256:6206db0af545d73d068add6d992279145f158428d1da6cc49adc4b630c5d6ee5", size = 6056688 }, + { url = "https://files.pythonhosted.org/packages/8f/7c/48f115d1c58a34032facebcd51fdf2d02df2c51d4a46a81dd1197bb2ea6b/numpy-2.5.0-cp312-cp312-win_amd64.whl", hash = "sha256:6f2d6873e2940c860a309d21e25b1e69af6aaffdd80aa056b04c16380db1c4f2", size = 12419237 }, + { url = "https://files.pythonhosted.org/packages/86/26/2e0882f4044d1b1a1b63e875151fb2393389032022a8b7f5657a7996d3b2/numpy-2.5.0-cp312-cp312-win_arm64.whl", hash = "sha256:a55e1eb2bca2cfd17a16b213c99dfc8502d47b0d494224d2122277d0400935ca", size = 10339912 }, + { url = "https://files.pythonhosted.org/packages/8a/33/07675aaad7f26ea013d5e884d9a0d784b79c6bd7566c333f5a52fa3c610b/numpy-2.5.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:520e6b8be0a4b65840ac8090d4f51cef4bed66e2b0894d5a520f099adc24a9b2", size = 16784890 }, + { url = "https://files.pythonhosted.org/packages/85/4b/953118a730ee3b35e28645e0eb4cf9beec5bdbb954e1ac2f5fcefba6bbc3/numpy-2.5.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:146b81cdd3967fdb6beca8ba25f00c58741d8f3cbd797f55af0fbe0bfec3469c", size = 11754584 }, + { url = "https://files.pythonhosted.org/packages/44/9b/56dd530c367c74ae17411027cea4135ca57e1e0583bf5594cee18bd83217/numpy-2.5.0-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:126b88d95e8ff9b00c9e717aa540469f21d6180162f84c0caec51b16215d49cd", size = 5313904 }, + { url = "https://files.pythonhosted.org/packages/ce/b0/bcd672edad27ecca7da1f7bb0ce72cd1706a4f2d79ae94990afc97c13e1c/numpy-2.5.0-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:d4313cef1594c5ce46c31b6e54e918338f63f16ee9322304e8c9114d6d81c8bd", size = 6648504 }, + { url = "https://files.pythonhosted.org/packages/80/9e/15cdfcbd30a1544a46c9e487a00df331c4672450216538705a9e51fa6710/numpy-2.5.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:750fb097caf26fa878746d9d119f6f9da12dedcbff1eea966c3e3447647c4a9e", size = 15150086 }, + { url = "https://files.pythonhosted.org/packages/32/4e/8d7656ccaab3e81e97258b8a9bc5f0c8502513a92fb4ceb0a2cbfebc17bf/numpy-2.5.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3893adc2dc7c0412ba76777db55a049215d99c9aa3113003be8f49f4f1290ab9", size = 16647250 }, + { url = "https://files.pythonhosted.org/packages/3c/81/97060281b602ed07f21b12f4ec409eac1f75a2f91fbc829ed8b2becf3ad4/numpy-2.5.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:835e454dd99b238cdc5a3f63bce2371296f5ebc53ca1e0f8e6ddbb6d92a29aab", size = 16512864 }, + { url = "https://files.pythonhosted.org/packages/33/ab/4496208146911f8d8ddb54f68a972aafa6c8d44babcb2ea03b0e5cc87c9d/numpy-2.5.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6f9836778081a0a3c02a6a21493f3e9f5b311f8d2541934f31f05583dc999ea4", size = 18408407 }, + { url = "https://files.pythonhosted.org/packages/d4/9f/a4df67c181e4ee8b467aa3332dc2db10fd5c515136831302f3ca48bc0a01/numpy-2.5.0-cp313-cp313-win32.whl", hash = "sha256:0b525be4744b60bb0557ac872d53ef07d085b5f39622bc579c98d3809d05b988", size = 6054431 }, + { url = "https://files.pythonhosted.org/packages/30/53/491e1c47c55b62ccc6a63c1c5b8635c73fc2258dddeb9bda27cae4a0ae96/numpy-2.5.0-cp313-cp313-win_amd64.whl", hash = "sha256:44353e2878930039db472b99dc353d749826e4010bd4d2a7f835e94a97a5c748", size = 12414420 }, + { url = "https://files.pythonhosted.org/packages/eb/4a/25c2906f541e9d9f4c5769764db732e6627be91a13f4724fa10634d77db4/numpy-2.5.0-cp313-cp313-win_arm64.whl", hash = "sha256:48f54b00711f83a5f796b70c518e8c2b3c5848dda03a54911f23eb68519b9b60", size = 10339533 }, + { url = "https://files.pythonhosted.org/packages/86/ad/abc44aaceaf7b17ee1edde2bbb4458da591bc79574cffff50c4bb35f00d1/numpy-2.5.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:f27582c55ba4c750b7c58c8faf021d2cd9324a662b466229db8a417b41368af9", size = 16783807 }, + { url = "https://files.pythonhosted.org/packages/5d/39/b72e168daf9c00fb20c9fc996d00437ccecdef3102387775d29d7a62576d/numpy-2.5.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:28e7137057d551e4a83c4ae414e3451f50568409db7569aacc7f9811ee06a446", size = 11765215 }, + { url = "https://files.pythonhosted.org/packages/f7/a0/8400a9c0e3625182347593f5e1f57da9a617a534794805c8df5518154ddc/numpy-2.5.0-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:e1da54b53e75cd9fcfc23efcc7edab2c6aecf97b6037566d8a0fe804af8ec57c", size = 5324493 }, + { url = "https://files.pythonhosted.org/packages/f6/8c/0d104deaa0401c93395a629ec902891618a2eff76d19229139cb5a887bfc/numpy-2.5.0-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:694d8f74e156f7fd01179f1aa8faa2f648ab6ae0f70b6c3fe57a03249aea2303", size = 6645211 }, + { url = "https://files.pythonhosted.org/packages/6a/d9/4a4a628c812750363786afc3d33492709a5cd64b215469c16b0f6c7bb811/numpy-2.5.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1a7569a7b53c77716f036bb28cb1c91f166a26ec7d9502cd1e4bdfe502fdec22", size = 15166004 }, + { url = "https://files.pythonhosted.org/packages/a0/5e/2a902317d7fc4aa93236e80c932662dadfc459b323d758329e01775125e1/numpy-2.5.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:39a0433bd4086ebd462960cf375e19195bb07b53dc1d87dd5fcf47ad78576f03", size = 16650797 }, + { url = "https://files.pythonhosted.org/packages/e9/a0/a0090e6329f4ca5992c07847bb579c5259a19953dc57255bb08793142ffb/numpy-2.5.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:929f0c79ac38bcbd7154fe631dc907abfeddbcc5027a896bd1f7767323271e7a", size = 16524647 }, + { url = "https://files.pythonhosted.org/packages/5e/7d/6caf27734c42b65837e7461ed0dbbd6b6fc835060c9714ec59d673bb383a/numpy-2.5.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:cc4f247a47bbf070bfd70be53ccdcf47b800af563535e7bbe172322197c30e21", size = 18411841 }, + { url = "https://files.pythonhosted.org/packages/13/dc/26edadbd812536769a82c2e9e002234e33feb5da43061d47a044f6d309b7/numpy-2.5.0-cp314-cp314-win32.whl", hash = "sha256:5dc71423499fab3f46f7a7201155ade1669ea101f2f429d332df9e72f8161731", size = 6106361 }, + { url = "https://files.pythonhosted.org/packages/f2/9e/4dd1459282229a72d92dece2ae9138e5cac94a72263a7ceb48f37434c925/numpy-2.5.0-cp314-cp314-win_amd64.whl", hash = "sha256:ebb81d9d5443e0309d6c54894c3fbed74ad7da0714352a67b6d773cd189eae73", size = 12551749 }, + { url = "https://files.pythonhosted.org/packages/05/a7/6bc6384c080b86c7f6c85c5bc5b540b24f4f679cd144791d99574e90d462/numpy-2.5.0-cp314-cp314-win_arm64.whl", hash = "sha256:3b94d0d0deceebfad3e67ae5c0e5eb87371e8f7a0581cd04a779928c2450cf1e", size = 10617072 }, + { url = "https://files.pythonhosted.org/packages/86/6b/4a2b71d66ada5608ae02b63f150dfad520f6940721cb7f029ad270befc0e/numpy-2.5.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:22f3d43e362d650bc39db1f17851302874a148ca95ba6981c1dfb5fa6862f35b", size = 11881067 }, + { url = "https://files.pythonhosted.org/packages/dc/b2/d365eb40a20efb49d67e9feb90494ed8511282ee1f5fa16006675c65397d/numpy-2.5.0-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:243563efb4cd7528a264567e9fd206c87826457322521d06206a00bfa316c927", size = 5440290 }, + { url = "https://files.pythonhosted.org/packages/fa/5e/e9c03188de5f9b767e46a8fe988bcfd3efad066a4a3fda8b9cb11a93f895/numpy-2.5.0-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:84881d825ca75249b189bbee875fcfe3238aa5c479e6100893cda566e8e86826", size = 6748371 }, + { url = "https://files.pythonhosted.org/packages/fd/1d/68c186a38a5027bae2c4ddd5ea681fdaf8b4d30fb7301def6d8ad270390f/numpy-2.5.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cda12aa4779d42b8771180aba759c96f527d43446d8f380ab59e2b35e8489efd", size = 15214643 }, + { url = "https://files.pythonhosted.org/packages/8c/67/73f67b7c7e20635baae9c4c3ead4ae7326a005900297a6110971abd62eb5/numpy-2.5.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1c0121101093d2bd74981b10f8837d78e794a8ff57834eb27179f49e1ba11ac6", size = 16690128 }, + { url = "https://files.pythonhosted.org/packages/eb/05/d4c1fb0c46d02a27d6b2b8b319a78c90937acec8631c1641874670b31e6f/numpy-2.5.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:d371c92cfa09da00022f501ab67fafaea813d752eb30ac44336d45b1e5b0268a", size = 16577902 }, + { url = "https://files.pythonhosted.org/packages/9e/1d/771c797d50fa26e4888989cccf1d50ee51f530d4e455ad2692dcb64fa711/numpy-2.5.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:9990713e9c38154c6861e7547f1e3fc7a87e75ff09bab24ef1cc81d81c2835e9", size = 18452814 }, + { url = "https://files.pythonhosted.org/packages/e8/46/52fc0d2a68d7643f0f149eeea5a5d8ea2a3507056ac8afa83c9212606e8b/numpy-2.5.0-cp314-cp314t-win32.whl", hash = "sha256:edadfbd4794b1086c0d822f81863e8a68fc129d132fd0bb9e31e955d7fbbbdb7", size = 6253168 }, + { url = "https://files.pythonhosted.org/packages/2a/be/6c8d1118b5f13b2881dc095d5b345de19c6638b8959c17409b6eff84c8aa/numpy-2.5.0-cp314-cp314t-win_amd64.whl", hash = "sha256:f7e5fa4382967ae6548bd2f174219afb908e294b0d5f625af01166edd5f7d9aa", size = 12736286 }, + { url = "https://files.pythonhosted.org/packages/fd/6a/d3a169aaf8536cf228d56a09e04bcb713a2fe4410d4e2105b9419b5a9c89/numpy-2.5.0-cp314-cp314t-win_arm64.whl", hash = "sha256:016623417bb330d719d579daf2d6b9a01ddc52e41a9ed61a47f39fde46dcd865", size = 10686451 }, +] + +[[package]] +name = "opencv-python" +version = "5.0.0.93" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.11.*'" }, + { name = "numpy", version = "2.5.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/79/4c/a438d23e09ce2033c09f7b784ad2fbdb0adf529e434101ed28f142226f98/opencv_python-5.0.0.93.tar.gz", hash = "sha256:66aac3e5b5faa48d4025816592f3af19e4bfc2c68dec067bae2dbb4ca10aa9e2", size = 81802749 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9c/75/76f6ade78f6102c61034f828e2a22616708df2c9504bc8d6af9dd8f73dc5/opencv_python-5.0.0.93-cp37-abi3-macosx_13_0_arm64.whl", hash = "sha256:198a75138241810206a17c829dbcc40a7cb1841cda538ca86cbbfc6c7d95f898", size = 48322443 }, + { url = "https://files.pythonhosted.org/packages/15/8c/bc1bda6aae69a32e9d84fc34153ba104cd25226861eb4aea33b2cea4860d/opencv_python-5.0.0.93-cp37-abi3-macosx_14_0_x86_64.whl", hash = "sha256:6bbc32f59e1b1a7db7b39c81f63d00625f041d333037fd8702f6da52cc39108b", size = 34782755 }, + { url = "https://files.pythonhosted.org/packages/f4/8a/b04776ec45d2dea08a1b176f1829201db3515d4ed16c35f8fcc9fa7beb16/opencv_python-5.0.0.93-cp37-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:e2b4272e736836f66c2d176e43ab8101f3a00d45654916399f52e150c58981ac", size = 50614064 }, + { url = "https://files.pythonhosted.org/packages/95/54/eb47866b94f2b5b42dde17644b78055ef1ee05aae59962c7290e55270803/opencv_python-5.0.0.93-cp37-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f8b6d0a212253dd26ad338c812f1f23ca118fdf05a9c8c6b9444f161aa8c5881", size = 71064711 }, + { url = "https://files.pythonhosted.org/packages/93/da/962579f1e703cbf8c5422fd1f576467dcb3b5b0b0b81c1471c979764353a/opencv_python-5.0.0.93-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:08d5d91d967b58d6db86073b2ad3eaef88ca4ebdfd45c9059bf59f5ded0c7ad2", size = 49798576 }, + { url = "https://files.pythonhosted.org/packages/cf/4c/c73f828fdbcd37eaf21d08fa852544a3ca7c2dbb3ea76873d64f2ea413d1/opencv_python-5.0.0.93-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:c8de2dec111122a02e8beb28e16c31904992dfd6186560b142a92c71403c1039", size = 73783032 }, + { url = "https://files.pythonhosted.org/packages/e2/4b/edaf83b996ca5a1a3d8ccad485706b9c6d4742b13b9c4586bf1c1e7d9423/opencv_python-5.0.0.93-cp37-abi3-win32.whl", hash = "sha256:4b4b1a34c79bf8d3738e3cfe9a9e67b51a79663f6b692cbdad8c31f570da4157", size = 35564734 }, + { url = "https://files.pythonhosted.org/packages/21/f0/9fa6e85cb10c8eb36a0222d27e50fe381b86ce49a55446bf39f491727564/opencv_python-5.0.0.93-cp37-abi3-win_amd64.whl", hash = "sha256:f90ba04b8f73bc5c3814037699739f0156f597338a98f05956c684e7c3ca10d2", size = 44000345 }, +] + +[[package]] +name = "packaging" +version = "26.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d7/f1/e7a6dd94a8d4a5626c03e4e99c87f241ba9e350cd9e6d75123f992427270/packaging-26.2.tar.gz", hash = "sha256:ff452ff5a3e828ce110190feff1178bb1f2ea2281fa2075aadb987c2fb221661", size = 228134 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/df/b2/87e62e8c3e2f4b32e5fe99e0b86d576da1312593b39f47d8ceef365e95ed/packaging-26.2-py3-none-any.whl", hash = "sha256:5fc45236b9446107ff2415ce77c807cee2862cb6fac22b8a73826d0693b0980e", size = 100195 }, +] + +[[package]] +name = "parso" +version = "0.8.7" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/30/4b/90c937815137d43ce71ba043cd3566221e9df6b9c805f24b5d138c9d40a7/parso-0.8.7.tar.gz", hash = "sha256:eaaac4c9fdd5e9e8852dc778d2d7405897ec510f2a298071453e5e3a07914bb1", size = 401824 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/99/5d/8268b644392ee874ee82a635cd0df1773de230bde356c38de28e298392cc/parso-0.8.7-py2.py3-none-any.whl", hash = "sha256:a8926eb2a1b915486941fdbd31e86a4baf88fe8c210f25f2f35ecec5b574ca1c", size = 107025 }, +] + +[[package]] +name = "pexpect" +version = "4.9.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "ptyprocess" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/42/92/cc564bf6381ff43ce1f4d06852fc19a2f11d180f23dc32d9588bee2f149d/pexpect-4.9.0.tar.gz", hash = "sha256:ee7d41123f3c9911050ea2c2dac107568dc43b2d3b0c7557a33212c398ead30f", size = 166450 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9e/c3/059298687310d527a58bb01f3b1965787ee3b40dce76752eda8b44e9a2c5/pexpect-4.9.0-py2.py3-none-any.whl", hash = "sha256:7236d1e080e4936be2dc3e326cec0af72acf9212a7e1d060210e70a47e253523", size = 63772 }, +] + +[[package]] +name = "pillow" +version = "12.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1c/3d/bb7fca845737cf9d7dbde16ed1843984665ff2e0a518f5db43e77ec540b9/pillow-12.3.0.tar.gz", hash = "sha256:3b8182a766685eaa002637e28b4ec8d6b18819a0c71f579bf0dbaa5830297cce", size = 47025035 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/25/c2/669d88644cddb1485bd9534e63e8cf476c8e51cb3c3a1297677023505c0e/pillow-12.3.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:6c0016e7b354317c4e9e525b937ac8596c38d2d232b419529b9cd7a1cd46e39a", size = 5392418 }, + { url = "https://files.pythonhosted.org/packages/6b/ba/3762f376a2948e3036488d773a146e0ae6ecc2ca03ac20e2615bd0b2ba02/pillow-12.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:bcc33feacfaefce60c12fd500a277533bdc02b10a19f7f6d348763d8140bbba7", size = 4785287 }, + { url = "https://files.pythonhosted.org/packages/07/50/b5d688cc9c52d4482f3d5bcab6ce20bc2a74a85d2343841c907444a3be2c/pillow-12.3.0-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5594fc43d548a7ed94949d139aa1341b270f1863f11cfd37f5a6c8b778a6b67f", size = 6253754 }, + { url = "https://files.pythonhosted.org/packages/4e/89/36f4cd76cf4baf05c50ababb976249153f18c959171c7f6ba09a6f217260/pillow-12.3.0-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f0606c8bf2cdefea14a43530f7657cbbb7ecf1c4222512492ef4a4434a9501ec", size = 6925605 }, + { url = "https://files.pythonhosted.org/packages/eb/c0/4de58cf6633b9e3a6061ef4be6fb91fc3c90b812ece886f531e3c523d777/pillow-12.3.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:85f998ea1848bc6757289e739cfbdda3a04adfd58b02fc018ce54d754a5ce468", size = 6327788 }, + { url = "https://files.pythonhosted.org/packages/87/3c/14d53682a19550dbbaf3b598f807d5457646c510805a44c7d7891cd1cd1a/pillow-12.3.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:25b9b82bb22e6e2b3cd07b39c68b7b862001226cb3dff7130d1cb914121b39ed", size = 7036288 }, + { url = "https://files.pythonhosted.org/packages/38/1d/36279e3c77efe034e4cc2b0393ee74ffdb5a62391dacbf9b916154f5f0b8/pillow-12.3.0-cp310-cp310-win32.whl", hash = "sha256:37dc8f7bbb66efe481bb60defacef820c950c24713fb44962ed6aa2a50966de1", size = 6472396 }, + { url = "https://files.pythonhosted.org/packages/48/7c/8fa0039574c476d7c6fa57dd7c32a130436877c6ec1e5ce1cc8ec44878c1/pillow-12.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:300557495eb45ebb8aec96c2da9c4be642fbf7cd937278b4013ba894ea8eb0eb", size = 7226887 }, + { url = "https://files.pythonhosted.org/packages/fa/17/e324be141d173c1c919428066c3259f21c1b8982e564e01a4a81e96dbdcf/pillow-12.3.0-cp310-cp310-win_arm64.whl", hash = "sha256:514435a37670e3e5e08f3945b68718b6ed329bb84367777e16f9f4dfe1e61a0f", size = 2568039 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { url = "https://files.pythonhosted.org/packages/86/a5/444817a4d4c4c2417df00513086ca196f388d8f9ef40c2e4ccd1ad1af54b/pillow-12.3.0-cp311-cp311-win32.whl", hash = "sha256:10e41f0fbf1eec8cfd234b8fe17a4caac7c9d0db4c204d3c173a8f9f6ef3232b", size = 6472232 }, + { url = "https://files.pythonhosted.org/packages/63/c6/4bad1b18d132a50b27e1365e1ab163616f7a5bb56d330f66f9d1d9d4f9d4/pillow-12.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:8e95e1385e4998ae9694eeaa4730ba5457ff61185b3a55e2e7bea0880aef452a", size = 7233653 }, + { url = "https://files.pythonhosted.org/packages/fd/16/00f91ab7760dc842f5aad55217e80fc4a7067a0604535249bc8a2d6d9870/pillow-12.3.0-cp311-cp311-win_arm64.whl", hash = "sha256:ebaea975e03d3141d9d3a507df75c9b3ec90fa9d2ffd07567b3a978d9d790b26", size = 2568195 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { url = "https://files.pythonhosted.org/packages/20/20/25e0f4dc178a6bc0696793720055519a0de89e7661dae886992decbd2f81/pillow-12.3.0-cp312-cp312-win32.whl", hash = "sha256:dbce0b29841537a2fa4a214c2bbf14de3587c9680caa9b4e217568472490b28f", size = 6472684 }, + { url = "https://files.pythonhosted.org/packages/45/89/da2f7971a317f83d807fdd4065c0af40208e59e692cc43d315a71a0e96d1/pillow-12.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:a2b55dd6b2a4c4b7d87ffa56bdb33fdc5fdb9a462173861a7bc097f17d91cb09", size = 7227137 }, + { url = "https://files.pythonhosted.org/packages/de/47/4845a0a6c0dbf1db8456bd9fc791f13c5ced7ced20606d08a0aacfd25b49/pillow-12.3.0-cp312-cp312-win_arm64.whl", hash = "sha256:331b624368d4f1d069149002f25f44bc61c8919ce8ddb3c45bdad8f6e2d89510", size = 2568267 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { url = "https://files.pythonhosted.org/packages/c9/f9/066794cca041b969964f779ee5fa66a9498bbf34248ac39c5d7954e4198f/pillow-12.3.0-cp313-cp313-win32.whl", hash = "sha256:a876864214e136f0eb367788dbd7df045f4806801518e2cfe9e13229cfe06d8f", size = 6473287 }, + { url = "https://files.pythonhosted.org/packages/a6/9b/7a58e61d62be561da3a356fe2384d4059a6345fc130e23ef1c36a5b81d24/pillow-12.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:1cca606cd25738df4ed873d5ad46bbdb3d83b5cbca291f6b4ff13a4df6b0bbe8", size = 7239691 }, + { url = "https://files.pythonhosted.org/packages/aa/b0/c4ed4f0ef8f8fa5ee8351537db6650bb8189f7e118842978dd6589065692/pillow-12.3.0-cp313-cp313-win_arm64.whl", hash = "sha256:b629de27fda84b42cde7edef0d85f13b958b47f6e9bbcbba9b673c562a89bd8b", size = 2568185 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { url = "https://files.pythonhosted.org/packages/a3/34/77f3f793fed8efc7d243f21b33c5a3f0d1c97ee70346d3db855587e155ff/pillow-12.3.0-cp314-cp314-win32.whl", hash = "sha256:af8d94b0db561cf68b88a267c5c44b49e134f525d0dc2cb7ed413a66bc23559a", size = 6467209 }, + { url = "https://files.pythonhosted.org/packages/f1/e0/492879f69d94f91f60fc8cd05ba03650e9520afebb2fb7aa12777d7c7f38/pillow-12.3.0-cp314-cp314-win_amd64.whl", hash = "sha256:fdafc9cce40277e0f7a0feabce0ee50dd2fa1800f3b38015e51296b5e814048d", size = 7237707 }, + { url = "https://files.pythonhosted.org/packages/c9/ac/6b11f2875f1c2ac040d84e1bbf9cf22a88038f901ca1037898b280b38365/pillow-12.3.0-cp314-cp314-win_arm64.whl", hash = "sha256:e91206ee562682b51b98ef4b26a6ef48fd84e15fd4c4bc5ec768eb641d206838", size = 2565995 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { url = "https://files.pythonhosted.org/packages/23/26/fcb2f6e37175b04f53570b59937867e2b80ee1685e744023153028fc14f9/pillow-12.3.0-cp314-cp314t-win32.whl", hash = "sha256:4b0a7fe987b14c31ebda6083f74f22b561fd3739bc0ac51e019622e3d72668c7", size = 6474125 }, + { url = "https://files.pythonhosted.org/packages/90/de/3634abee5f1c9e13c56787b7d5517b0ba8d6de51700b95578cf338349c9f/pillow-12.3.0-cp314-cp314t-win_amd64.whl", hash = "sha256:962864dc93511324d51ddbb5b9f8731bf71675b93ca612a07441896f4688fb8c", size = 7242939 }, + { url = "https://files.pythonhosted.org/packages/ce/2a/fd13f8eb24de5714a6eb444a3d67e2842c6c576e159a43793adf23051351/pillow-12.3.0-cp314-cp314t-win_arm64.whl", hash = "sha256:0740a512dc522224c77d9aa5a8d70d8b7d73fb91f2c21125d8d025d3b8990e45", size = 2567506 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { url = "https://files.pythonhosted.org/packages/54/76/a09cc3ccc8d773a7283d34c38bec1708f9e3cc932093cbc4c5e71ac4060b/pillow-12.3.0-cp315-cp315-win32.whl", hash = "sha256:57b3d78c95ba9059768b10e28b813002261d3f3dfc55cc48b0c988f625175827", size = 6467375 }, + { url = "https://files.pythonhosted.org/packages/3e/03/1846c49ba3b1d5550392a4bbd06d6fb4578e1cd91a803198b5c90f5f7d53/pillow-12.3.0-cp315-cp315-win_amd64.whl", hash = "sha256:fa4ecea169a355be7a3ade2c783e2ed12f0e40d2c5621cda8b3297faf7fbb9f5", size = 7237048 }, + { url = "https://files.pythonhosted.org/packages/fb/bb/89f35dcc79610423f9f195504d7def7f0d1416a711541b42867e25fe3412/pillow-12.3.0-cp315-cp315-win_arm64.whl", hash = "sha256:877c3f311ff35410f690861c4409e7ccbf0cd2f878e50628a28e5a0bb689e658", size = 2566006 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { url = "https://files.pythonhosted.org/packages/18/f0/a5595c1e8c3ae44b9828cb2f0fa8155e5095ef04d6327b8f61cf44a3df85/pillow-12.3.0-cp315-cp315t-win32.whl", hash = "sha256:1657923d2d45afb66526e5b933e5b3052e6bdea196c90d3abb2424e18c77dae8", size = 6474384 }, + { url = "https://files.pythonhosted.org/packages/e4/04/62bcd9f844984c5938d3b05264a61d797a29d3e0812341a8204af70bbdee/pillow-12.3.0-cp315-cp315t-win_amd64.whl", hash = "sha256:8cd2f7bdda092d99c9fc2fb7391354f306d01443d22785d0cbfafa2e2c8bb418", size = 7243537 }, + { url = "https://files.pythonhosted.org/packages/3d/68/1f3066acedf37673694a7141381d8f811ae97f30d34413d236abe7d489f1/pillow-12.3.0-cp315-cp315t-win_arm64.whl", hash = "sha256:06ff022112bc9cbf83b60f8e028d94ad87b60621706487e65f673de61610ab59", size = 2567491 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { url = "https://files.pythonhosted.org/packages/36/54/0169bc772ec491108b62f644f8ecf1fe5d8ae5ebafde2ee2142210166903/pillow-12.3.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:04f01d28a6aaff387bf842a13be313df23ba0597a44f1a976c9feb3c6ff4711a", size = 7231786 }, +] + +[[package]] +name = "prompt-toolkit" +version = "3.0.52" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "wcwidth" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a1/96/06e01a7b38dce6fe1db213e061a4602dd6032a8a97ef6c1a862537732421/prompt_toolkit-3.0.52.tar.gz", hash = "sha256:28cde192929c8e7321de85de1ddbe736f1375148b02f2e17edd840042b1be855", size = 434198 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/84/03/0d3ce49e2505ae70cf43bc5bb3033955d2fc9f932163e84dc0779cc47f48/prompt_toolkit-3.0.52-py3-none-any.whl", hash = "sha256:9aac639a3bbd33284347de5ad8d68ecc044b91a762dc39b7c21095fcd6a19955", size = 391431 }, +] + +[[package]] +name = "psutil" +version = "7.2.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/aa/c6/d1ddf4abb55e93cebc4f2ed8b5d6dbad109ecb8d63748dd2b20ab5e57ebe/psutil-7.2.2.tar.gz", hash = "sha256:0746f5f8d406af344fd547f1c8daa5f5c33dbc293bb8d6a16d80b4bb88f59372", size = 493740 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/51/08/510cbdb69c25a96f4ae523f733cdc963ae654904e8db864c07585ef99875/psutil-7.2.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:2edccc433cbfa046b980b0df0171cd25bcaeb3a68fe9022db0979e7aa74a826b", size = 130595 }, + { url = "https://files.pythonhosted.org/packages/d6/f5/97baea3fe7a5a9af7436301f85490905379b1c6f2dd51fe3ecf24b4c5fbf/psutil-7.2.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e78c8603dcd9a04c7364f1a3e670cea95d51ee865e4efb3556a3a63adef958ea", size = 131082 }, + { url = "https://files.pythonhosted.org/packages/37/d6/246513fbf9fa174af531f28412297dd05241d97a75911ac8febefa1a53c6/psutil-7.2.2-cp313-cp313t-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1a571f2330c966c62aeda00dd24620425d4b0cc86881c89861fbc04549e5dc63", size = 181476 }, + { url = "https://files.pythonhosted.org/packages/b8/b5/9182c9af3836cca61696dabe4fd1304e17bc56cb62f17439e1154f225dd3/psutil-7.2.2-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:917e891983ca3c1887b4ef36447b1e0873e70c933afc831c6b6da078ba474312", size = 184062 }, + { url = "https://files.pythonhosted.org/packages/16/ba/0756dca669f5a9300d0cbcbfae9a4c30e446dfc7440ffe43ded5724bfd93/psutil-7.2.2-cp313-cp313t-win_amd64.whl", hash = "sha256:ab486563df44c17f5173621c7b198955bd6b613fb87c71c161f827d3fb149a9b", size = 139893 }, + { url = "https://files.pythonhosted.org/packages/1c/61/8fa0e26f33623b49949346de05ec1ddaad02ed8ba64af45f40a147dbfa97/psutil-7.2.2-cp313-cp313t-win_arm64.whl", hash = "sha256:ae0aefdd8796a7737eccea863f80f81e468a1e4cf14d926bd9b6f5f2d5f90ca9", size = 135589 }, + { url = "https://files.pythonhosted.org/packages/81/69/ef179ab5ca24f32acc1dac0c247fd6a13b501fd5534dbae0e05a1c48b66d/psutil-7.2.2-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:eed63d3b4d62449571547b60578c5b2c4bcccc5387148db46e0c2313dad0ee00", size = 130664 }, + { url = "https://files.pythonhosted.org/packages/7b/64/665248b557a236d3fa9efc378d60d95ef56dd0a490c2cd37dafc7660d4a9/psutil-7.2.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7b6d09433a10592ce39b13d7be5a54fbac1d1228ed29abc880fb23df7cb694c9", size = 131087 }, + { url = "https://files.pythonhosted.org/packages/d5/2e/e6782744700d6759ebce3043dcfa661fb61e2fb752b91cdeae9af12c2178/psutil-7.2.2-cp314-cp314t-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1fa4ecf83bcdf6e6c8f4449aff98eefb5d0604bf88cb883d7da3d8d2d909546a", size = 182383 }, + { url = "https://files.pythonhosted.org/packages/57/49/0a41cefd10cb7505cdc04dab3eacf24c0c2cb158a998b8c7b1d27ee2c1f5/psutil-7.2.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e452c464a02e7dc7822a05d25db4cde564444a67e58539a00f929c51eddda0cf", size = 185210 }, + { url = "https://files.pythonhosted.org/packages/dd/2c/ff9bfb544f283ba5f83ba725a3c5fec6d6b10b8f27ac1dc641c473dc390d/psutil-7.2.2-cp314-cp314t-win_amd64.whl", hash = "sha256:c7663d4e37f13e884d13994247449e9f8f574bc4655d509c3b95e9ec9e2b9dc1", size = 141228 }, + { url = "https://files.pythonhosted.org/packages/f2/fc/f8d9c31db14fcec13748d373e668bc3bed94d9077dbc17fb0eebc073233c/psutil-7.2.2-cp314-cp314t-win_arm64.whl", hash = "sha256:11fe5a4f613759764e79c65cf11ebdf26e33d6dd34336f8a337aa2996d71c841", size = 136284 }, + { url = "https://files.pythonhosted.org/packages/e7/36/5ee6e05c9bd427237b11b3937ad82bb8ad2752d72c6969314590dd0c2f6e/psutil-7.2.2-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:ed0cace939114f62738d808fdcecd4c869222507e266e574799e9c0faa17d486", size = 129090 }, + { url = "https://files.pythonhosted.org/packages/80/c4/f5af4c1ca8c1eeb2e92ccca14ce8effdeec651d5ab6053c589b074eda6e1/psutil-7.2.2-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:1a7b04c10f32cc88ab39cbf606e117fd74721c831c98a27dc04578deb0c16979", size = 129859 }, + { url = "https://files.pythonhosted.org/packages/b5/70/5d8df3b09e25bce090399cf48e452d25c935ab72dad19406c77f4e828045/psutil-7.2.2-cp36-abi3-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:076a2d2f923fd4821644f5ba89f059523da90dc9014e85f8e45a5774ca5bc6f9", size = 155560 }, + { url = "https://files.pythonhosted.org/packages/63/65/37648c0c158dc222aba51c089eb3bdfa238e621674dc42d48706e639204f/psutil-7.2.2-cp36-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b0726cecd84f9474419d67252add4ac0cd9811b04d61123054b9fb6f57df6e9e", size = 156997 }, + { url = "https://files.pythonhosted.org/packages/8e/13/125093eadae863ce03c6ffdbae9929430d116a246ef69866dad94da3bfbc/psutil-7.2.2-cp36-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:fd04ef36b4a6d599bbdb225dd1d3f51e00105f6d48a28f006da7f9822f2606d8", size = 148972 }, + { url = "https://files.pythonhosted.org/packages/04/78/0acd37ca84ce3ddffaa92ef0f571e073faa6d8ff1f0559ab1272188ea2be/psutil-7.2.2-cp36-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:b58fabe35e80b264a4e3bb23e6b96f9e45a3df7fb7eed419ac0e5947c61e47cc", size = 148266 }, + { url = "https://files.pythonhosted.org/packages/b4/90/e2159492b5426be0c1fef7acba807a03511f97c5f86b3caeda6ad92351a7/psutil-7.2.2-cp37-abi3-win_amd64.whl", hash = "sha256:eb7e81434c8d223ec4a219b5fc1c47d0417b12be7ea866e24fb5ad6e84b3d988", size = 137737 }, + { url = "https://files.pythonhosted.org/packages/8c/c7/7bb2e321574b10df20cbde462a94e2b71d05f9bbda251ef27d104668306a/psutil-7.2.2-cp37-abi3-win_arm64.whl", hash = "sha256:8c233660f575a5a89e6d4cb65d9f938126312bca76d8fe087b947b3a1aaac9ee", size = 134617 }, +] + +[[package]] +name = "ptyprocess" +version = "0.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/20/e5/16ff212c1e452235a90aeb09066144d0c5a6a8c0834397e03f5224495c4e/ptyprocess-0.7.0.tar.gz", hash = "sha256:5c5d0a3b48ceee0b48485e0c26037c0acd7d29765ca3fbb5cb3831d347423220", size = 70762 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/22/a6/858897256d0deac81a172289110f31629fc4cee19b6f01283303e18c8db3/ptyprocess-0.7.0-py2.py3-none-any.whl", hash = "sha256:4b41f3967fce3af57cc7e94b888626c18bf37a083e3651ca8feeb66d492fef35", size = 13993 }, +] + +[[package]] +name = "pure-eval" +version = "0.2.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/cd/05/0a34433a064256a578f1783a10da6df098ceaa4a57bbeaa96a6c0352786b/pure_eval-0.2.3.tar.gz", hash = "sha256:5f4e983f40564c576c7c8635ae88db5956bb2229d7e9237d03b3c0b0190eaf42", size = 19752 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8e/37/efad0257dc6e593a18957422533ff0f87ede7c9c6ea010a2177d738fb82f/pure_eval-0.2.3-py3-none-any.whl", hash = "sha256:1db8e35b67b3d218d818ae653e27f06c3aa420901fa7b081ca98cbedc874e0d0", size = 11842 }, +] + +[[package]] +name = "pygeodesy" +version = "26.6.24" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/74/6b/a56c73cdec8d86d10bb78c59218af1e629861bf9b5397867d294152c2201/pygeodesy-26.6.24.tar.gz", hash = "sha256:d9d79e0522af7fc3728433b89ddab1449758a6cbd9bcd05ea29cf25a83b5d870", size = 9033914 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0a/b6/24fb52082c535ceead8f001b32a48fcd1623d50aa086a2b856b76cff4def/pygeodesy-26.6.24-py2.py3-none-any.whl", hash = "sha256:4928d670d7b606c71ad958eb7da7b9f01edb952e4639a92694c2d0121543fa72", size = 1117094 }, +] + +[[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 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f4/7e/a72dd26f3b0f4f2bf1dd8923c85f7ceb43172af56d63c7383eb62b332364/pygments-2.20.0-py3-none-any.whl", hash = "sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176", size = 1231151 }, +] + +[[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 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/10/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-py3-none-any.whl", hash = "sha256:850ba148bd908d7e2411587e247a1e4f0327839c40e2e5e6d05a007ecc69911d", size = 122781 }, +] + +[[package]] +name = "pyshp" +version = "3.1.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f5/58/8d9461f328b2878a4b1632db4189a68c77e0c87689830a7834f204302704/pyshp-3.1.4.tar.gz", hash = "sha256:0663762be72c67684c890decd888fb8e849e01a85ee6ef0346214b7329270453", size = 2225442 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2d/40/f1c777c07866b9de9420627c79bd22141268256d78afb890feabc4a85d70/pyshp-3.1.4-py3-none-any.whl", hash = "sha256:5bdd39c8fe02a47fe8de9c3355c5efaa312ff5c7fc6b8156592df738246f6167", size = 73189 }, +] + +[[package]] +name = "python-dateutil" +version = "2.9.0.post0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "six" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/66/c0/0c8b6ad9f17a802ee498c46e004a0eb49bc148f2fd230864601a86dcf6db/python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 342432 } +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 }, +] + +[[package]] +name = "pytz" +version = "2026.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ff/46/dd499ec9038423421951e4fad73051febaa13d2df82b4064f87af8b8c0c3/pytz-2026.2.tar.gz", hash = "sha256:0e60b47b29f21574376f218fe21abc009894a2321ea16c6754f3cad6eb7cdd6a", size = 320861 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ec/dd/96da98f892250475bdf2328112d7468abdd4acc7b902b6af23f4ed958ea0/pytz-2026.2-py2.py3-none-any.whl", hash = "sha256:04156e608bee23d3792fd45c94ae47fae1036688e75032eea2e3bf0323d1f126", size = 510141 }, +] + +[[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/f4/a0/39350dd17dd6d6c6507025c0e53aef67a9293a6d37d3511f23ea510d5800/pyyaml-6.0.3-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:214ed4befebe12df36bcc8bc2b64b396ca31be9304b8f59e25c11cf94a4c033b", size = 184227 }, + { url = "https://files.pythonhosted.org/packages/05/14/52d505b5c59ce73244f59c7a50ecf47093ce4765f116cdb98286a71eeca2/pyyaml-6.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:02ea2dfa234451bbb8772601d7b8e426c2bfa197136796224e50e35a78777956", size = 174019 }, + { url = "https://files.pythonhosted.org/packages/43/f7/0e6a5ae5599c838c696adb4e6330a59f463265bfa1e116cfd1fbb0abaaae/pyyaml-6.0.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b30236e45cf30d2b8e7b3e85881719e98507abed1011bf463a8fa23e9c3e98a8", size = 740646 }, + { url = "https://files.pythonhosted.org/packages/2f/3a/61b9db1d28f00f8fd0ae760459a5c4bf1b941baf714e207b6eb0657d2578/pyyaml-6.0.3-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:66291b10affd76d76f54fad28e22e51719ef9ba22b29e1d7d03d6777a9174198", size = 840793 }, + { url = "https://files.pythonhosted.org/packages/7a/1e/7acc4f0e74c4b3d9531e24739e0ab832a5edf40e64fbae1a9c01941cabd7/pyyaml-6.0.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9c7708761fccb9397fe64bbc0395abcae8c4bf7b0eac081e12b809bf47700d0b", size = 770293 }, + { url = "https://files.pythonhosted.org/packages/8b/ef/abd085f06853af0cd59fa5f913d61a8eab65d7639ff2a658d18a25d6a89d/pyyaml-6.0.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:418cf3f2111bc80e0933b2cd8cd04f286338bb88bdc7bc8e6dd775ebde60b5e0", size = 732872 }, + { url = "https://files.pythonhosted.org/packages/1f/15/2bc9c8faf6450a8b3c9fc5448ed869c599c0a74ba2669772b1f3a0040180/pyyaml-6.0.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:5e0b74767e5f8c593e8c9b5912019159ed0533c70051e9cce3e8b6aa699fcd69", size = 758828 }, + { url = "https://files.pythonhosted.org/packages/a3/00/531e92e88c00f4333ce359e50c19b8d1de9fe8d581b1534e35ccfbc5f393/pyyaml-6.0.3-cp310-cp310-win32.whl", hash = "sha256:28c8d926f98f432f88adc23edf2e6d4921ac26fb084b028c733d01868d19007e", size = 142415 }, + { url = "https://files.pythonhosted.org/packages/2a/fa/926c003379b19fca39dd4634818b00dec6c62d87faf628d1394e137354d4/pyyaml-6.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:bdb2c67c6c1390b63c6ff89f210c8fd09d9a1217a465701eac7316313c915e4c", size = 158561 }, + { 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 }, +] + +[[package]] +name = "rich" +version = "15.0.0" +source = { registry = "https://pypi.org/simple" } +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 }, +] + +[[package]] +name = "ruff" +version = "0.16.8" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ba/78/449cb84790bd5cc3823b2652ee405a4558856e5c4195aee3a16bf7b3eb5d/ruff-0.16.8.tar.gz", hash = "sha256:9247bf92b5f04d825c8639a4fe423ec2e4222acd9222e58412b0dab7e442798b", size = 4938814 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ac/25/6071aabc530e9be7e2c195e8fe3f7aea2735405b6cf447212832d7811831/ruff-0.16.8-py3-none-linux_armv6l.whl", hash = "sha256:6ffbd6d87383c1edf5f6fa890f10200950240d7c1a16052a19a09d3a2307dd38", size = 10048966 }, + { url = "https://files.pythonhosted.org/packages/54/98/07f90ecbc74dd5fb5764f11f2bc774d6a7cffef92d2ff5f5b4e9e23c754e/ruff-0.16.8-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:42ed6b878ed61e3acca92f2730a17acff39286944ea82398544696366a6f925e", size = 10165498 }, + { url = "https://files.pythonhosted.org/packages/fe/1f/e6a712e3b47cad4a40600134105ed193cb773f618a42eb7ba323cb812cc0/ruff-0.16.8-py3-none-macosx_11_0_arm64.whl", hash = "sha256:7ea781c7f2afba8c6a505ea0fb3f994020249e0c450635f5381286fea6b46170", size = 9830004 }, + { url = "https://files.pythonhosted.org/packages/23/f2/311a08776d75d81c7676e20b6b020ae63cbe881fcdc7a8dd64e6e18bdd93/ruff-0.16.8-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8efeae3bbe414a5efefda11a792dfb51ef90ac48d50c4830de2f644caf3e8659", size = 9986558 }, + { url = "https://files.pythonhosted.org/packages/f3/ed/37b6cb3d3ba8c73e68ae3eb1d502383beb5aa05a582bb7bb3a922f929f54/ruff-0.16.8-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3a79b795469fef7fc6e908b218eed2eb17332afd85031db6480dc864560e69b2", size = 9877332 }, + { url = "https://files.pythonhosted.org/packages/22/cc/40873a8f36ad084cc540d55fcca7077264d5b13b24659e9180c176fb2b08/ruff-0.16.8-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3fdc5563cdc50555e6fba39322850860e9267c1b3d12c26a74729d8604c3c812", size = 10507125 }, + { url = "https://files.pythonhosted.org/packages/c3/e4/fc91a642b78ccbab6b9477720f3644ae7a10a9bcce69a934679cd64f62bc/ruff-0.16.8-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:34508983c70665578dab88f5223d8e6228307e1135398ca8bfc8b7e9501e282b", size = 11336694 }, + { url = "https://files.pythonhosted.org/packages/c2/3d/bbd2a9a600a4e73dc3e7548a249c8d1671273464b55822c6fae50f602dff/ruff-0.16.8-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:644bb578569e0ffc575741232bd385dacdd6fbe123f1a729e7a225f54aa3957f", size = 10774448 }, + { url = "https://files.pythonhosted.org/packages/1a/41/d83af9879a7b6e8bf5fe16b1da0b134049d2f5d3afac12defb0897cb84bd/ruff-0.16.8-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15e7d226246961db9235098333caa13063906d3851136b84c2900b82f5daa1df", size = 10323796 }, + { url = "https://files.pythonhosted.org/packages/f5/2c/cefd07bfe914b84943ea769ade8d607bd22750b965d3228eefd7cebd15d0/ruff-0.16.8-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:a2bf6bc3e9ebdd4449abc6f06cf64b98051a2c61cf94d2fe9596518c881f1a1e", size = 10514115 }, + { url = "https://files.pythonhosted.org/packages/f3/9d/76a2e26c79a23be6e6e3664c57bec9e9fc8de155cfb9e4b67ea91b64f9d7/ruff-0.16.8-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:6ca111ba0849539165e9e59d2b442542f3c1e8060ebbdea82494f1ffbccb1e1f", size = 10072582 }, + { url = "https://files.pythonhosted.org/packages/2e/d4/f42edddb39668af1a559ceafa3823aedd65633a48dc9768e775485faa2c1/ruff-0.16.8-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:359a1e5b495448ee1e91018064382ebc86f90e8aac2fed222c7d0e4e8df85fd2", size = 9879644 }, + { url = "https://files.pythonhosted.org/packages/f8/d4/913e3195d95e0378786c6656945c865f534a3560e29139da4882aff630d1/ruff-0.16.8-py3-none-musllinux_1_2_i686.whl", hash = "sha256:59e8f5681349474110b24d62e93cfda6593f5fa3473446ca3705200cac1a08b9", size = 10231569 }, + { url = "https://files.pythonhosted.org/packages/2b/c4/8aa6ea0bdcedbd1bf87397e2fc4ed8406448ea5842f8660bc6e5f163039d/ruff-0.16.8-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:efa3e7a16d1baaa79957888dfdf8be9ef2e44db81cb032af06d76632ab59e773", size = 10663666 }, + { url = "https://files.pythonhosted.org/packages/3d/02/7f10ef4700bc223c30a3fdd10631a29830c45524b810a3c7ed947af64591/ruff-0.16.8-py3-none-win32.whl", hash = "sha256:55793ba85c69921e89be061426d91a78652d6e50317c962240922747a4eb713f", size = 10093472 }, + { url = "https://files.pythonhosted.org/packages/1e/5d/a509c07d714b6da88f2c518b4637cf6f1d46b074be8f0f1e5fb9ff5126fe/ruff-0.16.8-py3-none-win_amd64.whl", hash = "sha256:a6b85621fd3c81e31fc5f5add09c9c078b430db3595ca632efafdec9e64ebfaa", size = 10586899 }, + { url = "https://files.pythonhosted.org/packages/fe/a0/50787329e4f20bf9dc9f6230015d46ec69c51a97ace5bc202dae4755365d/ruff-0.16.8-py3-none-win_arm64.whl", hash = "sha256:d075e820af612102ce217f07cc93e69f9490b10ec13ea85fa87bd03d996cef8a", size = 10386316 }, +] + +[[package]] +name = "scipy" +version = "1.15.3" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.11'", +] +dependencies = [ + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/0f/37/6964b830433e654ec7485e45a00fc9a27cf868d622838f6b6d9c5ec0d532/scipy-1.15.3.tar.gz", hash = "sha256:eae3cf522bc7df64b42cad3925c876e1b0b6c35c1337c93e12c0f366f55b0eaf", size = 59419214 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/78/2f/4966032c5f8cc7e6a60f1b2e0ad686293b9474b65246b0c642e3ef3badd0/scipy-1.15.3-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:a345928c86d535060c9c2b25e71e87c39ab2f22fc96e9636bd74d1dbf9de448c", size = 38702770 }, + { url = "https://files.pythonhosted.org/packages/a0/6e/0c3bf90fae0e910c274db43304ebe25a6b391327f3f10b5dcc638c090795/scipy-1.15.3-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:ad3432cb0f9ed87477a8d97f03b763fd1d57709f1bbde3c9369b1dff5503b253", size = 30094511 }, + { url = "https://files.pythonhosted.org/packages/ea/b1/4deb37252311c1acff7f101f6453f0440794f51b6eacb1aad4459a134081/scipy-1.15.3-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:aef683a9ae6eb00728a542b796f52a5477b78252edede72b8327a886ab63293f", size = 22368151 }, + { url = "https://files.pythonhosted.org/packages/38/7d/f457626e3cd3c29b3a49ca115a304cebb8cc6f31b04678f03b216899d3c6/scipy-1.15.3-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:1c832e1bd78dea67d5c16f786681b28dd695a8cb1fb90af2e27580d3d0967e92", size = 25121732 }, + { url = "https://files.pythonhosted.org/packages/db/0a/92b1de4a7adc7a15dcf5bddc6e191f6f29ee663b30511ce20467ef9b82e4/scipy-1.15.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:263961f658ce2165bbd7b99fa5135195c3a12d9bef045345016b8b50c315cb82", size = 35547617 }, + { url = "https://files.pythonhosted.org/packages/8e/6d/41991e503e51fc1134502694c5fa7a1671501a17ffa12716a4a9151af3df/scipy-1.15.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9e2abc762b0811e09a0d3258abee2d98e0c703eee49464ce0069590846f31d40", size = 37662964 }, + { url = "https://files.pythonhosted.org/packages/25/e1/3df8f83cb15f3500478c889be8fb18700813b95e9e087328230b98d547ff/scipy-1.15.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:ed7284b21a7a0c8f1b6e5977ac05396c0d008b89e05498c8b7e8f4a1423bba0e", size = 37238749 }, + { url = "https://files.pythonhosted.org/packages/93/3e/b3257cf446f2a3533ed7809757039016b74cd6f38271de91682aa844cfc5/scipy-1.15.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:5380741e53df2c566f4d234b100a484b420af85deb39ea35a1cc1be84ff53a5c", size = 40022383 }, + { url = "https://files.pythonhosted.org/packages/d1/84/55bc4881973d3f79b479a5a2e2df61c8c9a04fcb986a213ac9c02cfb659b/scipy-1.15.3-cp310-cp310-win_amd64.whl", hash = "sha256:9d61e97b186a57350f6d6fd72640f9e99d5a4a2b8fbf4b9ee9a841eab327dc13", size = 41259201 }, + { url = "https://files.pythonhosted.org/packages/96/ab/5cc9f80f28f6a7dff646c5756e559823614a42b1939d86dd0ed550470210/scipy-1.15.3-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:993439ce220d25e3696d1b23b233dd010169b62f6456488567e830654ee37a6b", size = 38714255 }, + { url = "https://files.pythonhosted.org/packages/4a/4a/66ba30abe5ad1a3ad15bfb0b59d22174012e8056ff448cb1644deccbfed2/scipy-1.15.3-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:34716e281f181a02341ddeaad584205bd2fd3c242063bd3423d61ac259ca7eba", size = 30111035 }, + { url = "https://files.pythonhosted.org/packages/4b/fa/a7e5b95afd80d24313307f03624acc65801846fa75599034f8ceb9e2cbf6/scipy-1.15.3-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:3b0334816afb8b91dab859281b1b9786934392aa3d527cd847e41bb6f45bee65", size = 22384499 }, + { url = "https://files.pythonhosted.org/packages/17/99/f3aaddccf3588bb4aea70ba35328c204cadd89517a1612ecfda5b2dd9d7a/scipy-1.15.3-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:6db907c7368e3092e24919b5e31c76998b0ce1684d51a90943cb0ed1b4ffd6c1", size = 25152602 }, + { url = "https://files.pythonhosted.org/packages/56/c5/1032cdb565f146109212153339f9cb8b993701e9fe56b1c97699eee12586/scipy-1.15.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:721d6b4ef5dc82ca8968c25b111e307083d7ca9091bc38163fb89243e85e3889", size = 35503415 }, + { url = "https://files.pythonhosted.org/packages/bd/37/89f19c8c05505d0601ed5650156e50eb881ae3918786c8fd7262b4ee66d3/scipy-1.15.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:39cb9c62e471b1bb3750066ecc3a3f3052b37751c7c3dfd0fd7e48900ed52982", size = 37652622 }, + { url = "https://files.pythonhosted.org/packages/7e/31/be59513aa9695519b18e1851bb9e487de66f2d31f835201f1b42f5d4d475/scipy-1.15.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:795c46999bae845966368a3c013e0e00947932d68e235702b5c3f6ea799aa8c9", size = 37244796 }, + { url = "https://files.pythonhosted.org/packages/10/c0/4f5f3eeccc235632aab79b27a74a9130c6c35df358129f7ac8b29f562ac7/scipy-1.15.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:18aaacb735ab38b38db42cb01f6b92a2d0d4b6aabefeb07f02849e47f8fb3594", size = 40047684 }, + { url = "https://files.pythonhosted.org/packages/ab/a7/0ddaf514ce8a8714f6ed243a2b391b41dbb65251affe21ee3077ec45ea9a/scipy-1.15.3-cp311-cp311-win_amd64.whl", hash = "sha256:ae48a786a28412d744c62fd7816a4118ef97e5be0bee968ce8f0a2fba7acf3bb", size = 41246504 }, + { url = "https://files.pythonhosted.org/packages/37/4b/683aa044c4162e10ed7a7ea30527f2cbd92e6999c10a8ed8edb253836e9c/scipy-1.15.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6ac6310fdbfb7aa6612408bd2f07295bcbd3fda00d2d702178434751fe48e019", size = 38766735 }, + { url = "https://files.pythonhosted.org/packages/7b/7e/f30be3d03de07f25dc0ec926d1681fed5c732d759ac8f51079708c79e680/scipy-1.15.3-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:185cd3d6d05ca4b44a8f1595af87f9c372bb6acf9c808e99aa3e9aa03bd98cf6", size = 30173284 }, + { url = "https://files.pythonhosted.org/packages/07/9c/0ddb0d0abdabe0d181c1793db51f02cd59e4901da6f9f7848e1f96759f0d/scipy-1.15.3-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:05dc6abcd105e1a29f95eada46d4a3f251743cfd7d3ae8ddb4088047f24ea477", size = 22446958 }, + { url = "https://files.pythonhosted.org/packages/af/43/0bce905a965f36c58ff80d8bea33f1f9351b05fad4beaad4eae34699b7a1/scipy-1.15.3-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:06efcba926324df1696931a57a176c80848ccd67ce6ad020c810736bfd58eb1c", size = 25242454 }, + { url = "https://files.pythonhosted.org/packages/56/30/a6f08f84ee5b7b28b4c597aca4cbe545535c39fe911845a96414700b64ba/scipy-1.15.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c05045d8b9bfd807ee1b9f38761993297b10b245f012b11b13b91ba8945f7e45", size = 35210199 }, + { url = "https://files.pythonhosted.org/packages/0b/1f/03f52c282437a168ee2c7c14a1a0d0781a9a4a8962d84ac05c06b4c5b555/scipy-1.15.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:271e3713e645149ea5ea3e97b57fdab61ce61333f97cfae392c28ba786f9bb49", size = 37309455 }, + { url = "https://files.pythonhosted.org/packages/89/b1/fbb53137f42c4bf630b1ffdfc2151a62d1d1b903b249f030d2b1c0280af8/scipy-1.15.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6cfd56fc1a8e53f6e89ba3a7a7251f7396412d655bca2aa5611c8ec9a6784a1e", size = 36885140 }, + { url = "https://files.pythonhosted.org/packages/2e/2e/025e39e339f5090df1ff266d021892694dbb7e63568edcfe43f892fa381d/scipy-1.15.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:0ff17c0bb1cb32952c09217d8d1eed9b53d1463e5f1dd6052c7857f83127d539", size = 39710549 }, + { url = "https://files.pythonhosted.org/packages/e6/eb/3bf6ea8ab7f1503dca3a10df2e4b9c3f6b3316df07f6c0ded94b281c7101/scipy-1.15.3-cp312-cp312-win_amd64.whl", hash = "sha256:52092bc0472cfd17df49ff17e70624345efece4e1a12b23783a1ac59a1b728ed", size = 40966184 }, + { url = "https://files.pythonhosted.org/packages/73/18/ec27848c9baae6e0d6573eda6e01a602e5649ee72c27c3a8aad673ebecfd/scipy-1.15.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:2c620736bcc334782e24d173c0fdbb7590a0a436d2fdf39310a8902505008759", size = 38728256 }, + { url = "https://files.pythonhosted.org/packages/74/cd/1aef2184948728b4b6e21267d53b3339762c285a46a274ebb7863c9e4742/scipy-1.15.3-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:7e11270a000969409d37ed399585ee530b9ef6aa99d50c019de4cb01e8e54e62", size = 30109540 }, + { url = "https://files.pythonhosted.org/packages/5b/d8/59e452c0a255ec352bd0a833537a3bc1bfb679944c4938ab375b0a6b3a3e/scipy-1.15.3-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:8c9ed3ba2c8a2ce098163a9bdb26f891746d02136995df25227a20e71c396ebb", size = 22383115 }, + { url = "https://files.pythonhosted.org/packages/08/f5/456f56bbbfccf696263b47095291040655e3cbaf05d063bdc7c7517f32ac/scipy-1.15.3-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:0bdd905264c0c9cfa74a4772cdb2070171790381a5c4d312c973382fc6eaf730", size = 25163884 }, + { url = "https://files.pythonhosted.org/packages/a2/66/a9618b6a435a0f0c0b8a6d0a2efb32d4ec5a85f023c2b79d39512040355b/scipy-1.15.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:79167bba085c31f38603e11a267d862957cbb3ce018d8b38f79ac043bc92d825", size = 35174018 }, + { url = "https://files.pythonhosted.org/packages/b5/09/c5b6734a50ad4882432b6bb7c02baf757f5b2f256041da5df242e2d7e6b6/scipy-1.15.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c9deabd6d547aee2c9a81dee6cc96c6d7e9a9b1953f74850c179f91fdc729cb7", size = 37269716 }, + { url = "https://files.pythonhosted.org/packages/77/0a/eac00ff741f23bcabd352731ed9b8995a0a60ef57f5fd788d611d43d69a1/scipy-1.15.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:dde4fc32993071ac0c7dd2d82569e544f0bdaff66269cb475e0f369adad13f11", size = 36872342 }, + { url = "https://files.pythonhosted.org/packages/fe/54/4379be86dd74b6ad81551689107360d9a3e18f24d20767a2d5b9253a3f0a/scipy-1.15.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f77f853d584e72e874d87357ad70f44b437331507d1c311457bed8ed2b956126", size = 39670869 }, + { url = "https://files.pythonhosted.org/packages/87/2e/892ad2862ba54f084ffe8cc4a22667eaf9c2bcec6d2bff1d15713c6c0703/scipy-1.15.3-cp313-cp313-win_amd64.whl", hash = "sha256:b90ab29d0c37ec9bf55424c064312930ca5f4bde15ee8619ee44e69319aab163", size = 40988851 }, + { url = "https://files.pythonhosted.org/packages/1b/e9/7a879c137f7e55b30d75d90ce3eb468197646bc7b443ac036ae3fe109055/scipy-1.15.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:3ac07623267feb3ae308487c260ac684b32ea35fd81e12845039952f558047b8", size = 38863011 }, + { url = "https://files.pythonhosted.org/packages/51/d1/226a806bbd69f62ce5ef5f3ffadc35286e9fbc802f606a07eb83bf2359de/scipy-1.15.3-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:6487aa99c2a3d509a5227d9a5e889ff05830a06b2ce08ec30df6d79db5fcd5c5", size = 30266407 }, + { url = "https://files.pythonhosted.org/packages/e5/9b/f32d1d6093ab9eeabbd839b0f7619c62e46cc4b7b6dbf05b6e615bbd4400/scipy-1.15.3-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:50f9e62461c95d933d5c5ef4a1f2ebf9a2b4e83b0db374cb3f1de104d935922e", size = 22540030 }, + { url = "https://files.pythonhosted.org/packages/e7/29/c278f699b095c1a884f29fda126340fcc201461ee8bfea5c8bdb1c7c958b/scipy-1.15.3-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:14ed70039d182f411ffc74789a16df3835e05dc469b898233a245cdfd7f162cb", size = 25218709 }, + { url = "https://files.pythonhosted.org/packages/24/18/9e5374b617aba742a990581373cd6b68a2945d65cc588482749ef2e64467/scipy-1.15.3-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0a769105537aa07a69468a0eefcd121be52006db61cdd8cac8a0e68980bbb723", size = 34809045 }, + { url = "https://files.pythonhosted.org/packages/e1/fe/9c4361e7ba2927074360856db6135ef4904d505e9b3afbbcb073c4008328/scipy-1.15.3-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9db984639887e3dffb3928d118145ffe40eff2fa40cb241a306ec57c219ebbbb", size = 36703062 }, + { url = "https://files.pythonhosted.org/packages/b7/8e/038ccfe29d272b30086b25a4960f757f97122cb2ec42e62b460d02fe98e9/scipy-1.15.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:40e54d5c7e7ebf1aa596c374c49fa3135f04648a0caabcb66c52884b943f02b4", size = 36393132 }, + { url = "https://files.pythonhosted.org/packages/10/7e/5c12285452970be5bdbe8352c619250b97ebf7917d7a9a9e96b8a8140f17/scipy-1.15.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:5e721fed53187e71d0ccf382b6bf977644c533e506c4d33c3fb24de89f5c3ed5", size = 38979503 }, + { url = "https://files.pythonhosted.org/packages/81/06/0a5e5349474e1cbc5757975b21bd4fad0e72ebf138c5592f191646154e06/scipy-1.15.3-cp313-cp313t-win_amd64.whl", hash = "sha256:76ad1fb5f8752eabf0fa02e4cc0336b4e8f021e2d5f061ed37d6d264db35e3ca", size = 40308097 }, +] + +[[package]] +name = "scipy" +version = "1.17.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version == '3.11.*'", +] +dependencies = [ + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.11.*'" }, +] +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 }, +] + +[[package]] +name = "scipy" +version = "1.18.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version == '3.12.*'", + "python_full_version >= '3.13'", +] +dependencies = [ + { name = "numpy", version = "2.5.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a7/25/c2700dfaf6442b4effaa91af24ebce5dc9d31bb4a69706313aae70d72cd0/scipy-1.18.0.tar.gz", hash = "sha256:67b2ad2ad54c72ca6d04975a9b2df8c3638c34ddd5b28738e94fc2b57929d378", size = 30774447 } +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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { url = "https://files.pythonhosted.org/packages/2b/54/9a9edb45345bd6744da5ddfb6628e5d5185920494c6a67ec45b6381004cb/scipy-1.18.0-cp312-cp312-win_amd64.whl", hash = "sha256:71ccc8faa2dd16ac310233203474a8b5cb67f10dedd54a3116d34943f4b19132", size = 36597428 }, + { url = "https://files.pythonhosted.org/packages/99/0e/33f32a2a58987e26aec0f7df252cbbad1e90ae77bdbc76f40dd4ed0cf0ea/scipy-1.18.0-cp312-cp312-win_arm64.whl", hash = "sha256:d88363fd9d8fbd3511bd273f1a49efb2a540773ddf92a91d57498ce7dd7f3e76", size = 24351481 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { url = "https://files.pythonhosted.org/packages/b7/d2/ae881ee28d014f38e0ccbfd974a06a919ba9af34f1f74bf42b5301891d63/scipy-1.18.0-cp313-cp313-win_amd64.whl", hash = "sha256:1afac4a847207c7ff8efd321734a50b06d0280b3b2a2c0fc2f413101747ad7c7", size = 36554943 }, + { url = "https://files.pythonhosted.org/packages/70/3a/21154e2d54eb3639c6bf4dbae2e531c68356bfe95990daa30df33b30d556/scipy-1.18.0-cp313-cp313-win_arm64.whl", hash = "sha256:c5dbddf60e58c2312316d097271a8e73d40eaf2eabfa4d95ed7d3695bbf2ce7b", size = 24350911 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { url = "https://files.pythonhosted.org/packages/93/3e/902d836831474b0ab5a37d16404f7bc5fafd9efba632890e271ba952635f/scipy-1.18.0-cp314-cp314-win_amd64.whl", hash = "sha256:2d8bbdc6c817f5b4006a54d799d4f5bab6f910193cbb9a1ff310833d4d270f61", size = 37288845 }, + { url = "https://files.pythonhosted.org/packages/b6/43/8d73b337a3bdb14daa0314f0434210747c02d79d729ce1777574a817dcf6/scipy-1.18.0-cp314-cp314-win_arm64.whl", hash = "sha256:18e9575f1569b2c54174e6159d32942e03731177f63dce7975f0a0c88d102f5b", size = 24988971 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { url = "https://files.pythonhosted.org/packages/31/8e/f04c68e39919a010d34f2ee1367fd705b0a25a02f609d755f0bfbc0a15fc/scipy-1.18.0-cp314-cp314t-win_amd64.whl", hash = "sha256:e40baea28ae7f5475c779741e2d90b1247c78531207b49c7030e698ff81cee3f", size = 37365390 }, + { url = "https://files.pythonhosted.org/packages/d5/19/969dc072906c84dd0a3b05dcf57ea750936087d7873549e408b35cfc3f97/scipy-1.18.0-cp314-cp314t-win_arm64.whl", hash = "sha256:368e0a705903c466aa5f08eefb39e6b1b6b2d659e7352a31fd9e2438365be0f8", size = 25279661 }, +] + +[[package]] +name = "scriptconfig" +version = "0.9.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyyaml", marker = "python_full_version < '4.0'" }, + { name = "ubelt" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a4/4c/6d0ecc5e29b498dc6b5402a0c561e97dd00ce1aeebdc5e494cc5fa8a0b6b/scriptconfig-0.9.1.tar.gz", hash = "sha256:9766c2ee601c4b8d97753ba13a914f8c0a212da4f6d2aec18ad628fd8b53d7ef", size = 112424 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/57/00/2bf691dfc2b0170013d466fcdbbfc9d036e26d6049d74efaf7775401321d/scriptconfig-0.9.1-py3-none-any.whl", hash = "sha256:4b69e2f4eb681ec4480e9bb5d6c82d2f8c82a9badc703a261ee8940736101d86", size = 87103 }, +] + +[[package]] +name = "shapely" +version = "2.1.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.11.*'" }, + { name = "numpy", version = "2.5.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/4d/bc/0989043118a27cccb4e906a46b7565ce36ca7b57f5a18b78f4f1b0f72d9d/shapely-2.1.2.tar.gz", hash = "sha256:2ed4ecb28320a433db18a5bf029986aa8afcfd740745e78847e330d5d94922a9", size = 315489 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/05/89/c3548aa9b9812a5d143986764dededfa48d817714e947398bdda87c77a72/shapely-2.1.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7ae48c236c0324b4e139bea88a306a04ca630f49be66741b340729d380d8f52f", size = 1825959 }, + { url = "https://files.pythonhosted.org/packages/ce/8a/7ebc947080442edd614ceebe0ce2cdbd00c25e832c240e1d1de61d0e6b38/shapely-2.1.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:eba6710407f1daa8e7602c347dfc94adc02205ec27ed956346190d66579eb9ea", size = 1629196 }, + { url = "https://files.pythonhosted.org/packages/c8/86/c9c27881c20d00fc409e7e059de569d5ed0abfcec9c49548b124ebddea51/shapely-2.1.2-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ef4a456cc8b7b3d50ccec29642aa4aeda959e9da2fe9540a92754770d5f0cf1f", size = 2951065 }, + { url = "https://files.pythonhosted.org/packages/50/8a/0ab1f7433a2a85d9e9aea5b1fbb333f3b09b309e7817309250b4b7b2cc7a/shapely-2.1.2-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:e38a190442aacc67ff9f75ce60aec04893041f16f97d242209106d502486a142", size = 3058666 }, + { url = "https://files.pythonhosted.org/packages/bb/c6/5a30ffac9c4f3ffd5b7113a7f5299ccec4713acd5ee44039778a7698224e/shapely-2.1.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:40d784101f5d06a1fd30b55fc11ea58a61be23f930d934d86f19a180909908a4", size = 3966905 }, + { url = "https://files.pythonhosted.org/packages/9c/72/e92f3035ba43e53959007f928315a68fbcf2eeb4e5ededb6f0dc7ff1ecc3/shapely-2.1.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:f6f6cd5819c50d9bcf921882784586aab34a4bd53e7553e175dece6db513a6f0", size = 4129260 }, + { url = "https://files.pythonhosted.org/packages/42/24/605901b73a3d9f65fa958e63c9211f4be23d584da8a1a7487382fac7fdc5/shapely-2.1.2-cp310-cp310-win32.whl", hash = "sha256:fe9627c39c59e553c90f5bc3128252cb85dc3b3be8189710666d2f8bc3a5503e", size = 1544301 }, + { url = "https://files.pythonhosted.org/packages/e1/89/6db795b8dd3919851856bd2ddd13ce434a748072f6fdee42ff30cbd3afa3/shapely-2.1.2-cp310-cp310-win_amd64.whl", hash = "sha256:1d0bfb4b8f661b3b4ec3565fa36c340bfb1cda82087199711f86a88647d26b2f", size = 1722074 }, + { url = "https://files.pythonhosted.org/packages/8f/8d/1ff672dea9ec6a7b5d422eb6d095ed886e2e523733329f75fdcb14ee1149/shapely-2.1.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:91121757b0a36c9aac3427a651a7e6567110a4a67c97edf04f8d55d4765f6618", size = 1820038 }, + { url = "https://files.pythonhosted.org/packages/4f/ce/28fab8c772ce5db23a0d86bf0adaee0c4c79d5ad1db766055fa3dab442e2/shapely-2.1.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:16a9c722ba774cf50b5d4541242b4cce05aafd44a015290c82ba8a16931ff63d", size = 1626039 }, + { url = "https://files.pythonhosted.org/packages/70/8b/868b7e3f4982f5006e9395c1e12343c66a8155c0374fdc07c0e6a1ab547d/shapely-2.1.2-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:cc4f7397459b12c0b196c9efe1f9d7e92463cbba142632b4cc6d8bbbbd3e2b09", size = 3001519 }, + { url = "https://files.pythonhosted.org/packages/13/02/58b0b8d9c17c93ab6340edd8b7308c0c5a5b81f94ce65705819b7416dba5/shapely-2.1.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:136ab87b17e733e22f0961504d05e77e7be8c9b5a8184f685b4a91a84efe3c26", size = 3110842 }, + { url = "https://files.pythonhosted.org/packages/af/61/8e389c97994d5f331dcffb25e2fa761aeedfb52b3ad9bcdd7b8671f4810a/shapely-2.1.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:16c5d0fc45d3aa0a69074979f4f1928ca2734fb2e0dde8af9611e134e46774e7", size = 4021316 }, + { url = "https://files.pythonhosted.org/packages/d3/d4/9b2a9fe6039f9e42ccf2cb3e84f219fd8364b0c3b8e7bbc857b5fbe9c14c/shapely-2.1.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:6ddc759f72b5b2b0f54a7e7cde44acef680a55019eb52ac63a7af2cf17cb9cd2", size = 4178586 }, + { url = "https://files.pythonhosted.org/packages/16/f6/9840f6963ed4decf76b08fd6d7fed14f8779fb7a62cb45c5617fa8ac6eab/shapely-2.1.2-cp311-cp311-win32.whl", hash = "sha256:2fa78b49485391224755a856ed3b3bd91c8455f6121fee0db0e71cefb07d0ef6", size = 1543961 }, + { url = "https://files.pythonhosted.org/packages/38/1e/3f8ea46353c2a33c1669eb7327f9665103aa3a8dfe7f2e4ef714c210b2c2/shapely-2.1.2-cp311-cp311-win_amd64.whl", hash = "sha256:c64d5c97b2f47e3cd9b712eaced3b061f2b71234b3fc263e0fcf7d889c6559dc", size = 1722856 }, + { url = "https://files.pythonhosted.org/packages/24/c0/f3b6453cf2dfa99adc0ba6675f9aaff9e526d2224cbd7ff9c1a879238693/shapely-2.1.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:fe2533caae6a91a543dec62e8360fe86ffcdc42a7c55f9dfd0128a977a896b94", size = 1833550 }, + { url = "https://files.pythonhosted.org/packages/86/07/59dee0bc4b913b7ab59ab1086225baca5b8f19865e6101db9ebb7243e132/shapely-2.1.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ba4d1333cc0bc94381d6d4308d2e4e008e0bd128bdcff5573199742ee3634359", size = 1643556 }, + { url = "https://files.pythonhosted.org/packages/26/29/a5397e75b435b9895cd53e165083faed5d12fd9626eadec15a83a2411f0f/shapely-2.1.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:0bd308103340030feef6c111d3eb98d50dc13feea33affc8a6f9fa549e9458a3", size = 2988308 }, + { url = "https://files.pythonhosted.org/packages/b9/37/e781683abac55dde9771e086b790e554811a71ed0b2b8a1e789b7430dd44/shapely-2.1.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1e7d4d7ad262a48bb44277ca12c7c78cb1b0f56b32c10734ec9a1d30c0b0c54b", size = 3099844 }, + { url = "https://files.pythonhosted.org/packages/d8/f3/9876b64d4a5a321b9dc482c92bb6f061f2fa42131cba643c699f39317cb9/shapely-2.1.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e9eddfe513096a71896441a7c37db72da0687b34752c4e193577a145c71736fc", size = 3988842 }, + { url = "https://files.pythonhosted.org/packages/d1/a0/704c7292f7014c7e74ec84eddb7b109e1fbae74a16deae9c1504b1d15565/shapely-2.1.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:980c777c612514c0cf99bc8a9de6d286f5e186dcaf9091252fcd444e5638193d", size = 4152714 }, + { url = "https://files.pythonhosted.org/packages/53/46/319c9dc788884ad0785242543cdffac0e6530e4d0deb6c4862bc4143dcf3/shapely-2.1.2-cp312-cp312-win32.whl", hash = "sha256:9111274b88e4d7b54a95218e243282709b330ef52b7b86bc6aaf4f805306f454", size = 1542745 }, + { url = "https://files.pythonhosted.org/packages/ec/bf/cb6c1c505cb31e818e900b9312d514f381fbfa5c4363edfce0fcc4f8c1a4/shapely-2.1.2-cp312-cp312-win_amd64.whl", hash = "sha256:743044b4cfb34f9a67205cee9279feaf60ba7d02e69febc2afc609047cb49179", size = 1722861 }, + { url = "https://files.pythonhosted.org/packages/c3/90/98ef257c23c46425dc4d1d31005ad7c8d649fe423a38b917db02c30f1f5a/shapely-2.1.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b510dda1a3672d6879beb319bc7c5fd302c6c354584690973c838f46ec3e0fa8", size = 1832644 }, + { url = "https://files.pythonhosted.org/packages/6d/ab/0bee5a830d209adcd3a01f2d4b70e587cdd9fd7380d5198c064091005af8/shapely-2.1.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:8cff473e81017594d20ec55d86b54bc635544897e13a7cfc12e36909c5309a2a", size = 1642887 }, + { url = "https://files.pythonhosted.org/packages/2d/5e/7d7f54ba960c13302584c73704d8c4d15404a51024631adb60b126a4ae88/shapely-2.1.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:fe7b77dc63d707c09726b7908f575fc04ff1d1ad0f3fb92aec212396bc6cfe5e", size = 2970931 }, + { url = "https://files.pythonhosted.org/packages/f2/a2/83fc37e2a58090e3d2ff79175a95493c664bcd0b653dd75cb9134645a4e5/shapely-2.1.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7ed1a5bbfb386ee8332713bf7508bc24e32d24b74fc9a7b9f8529a55db9f4ee6", size = 3082855 }, + { url = "https://files.pythonhosted.org/packages/44/2b/578faf235a5b09f16b5f02833c53822294d7f21b242f8e2d0cf03fb64321/shapely-2.1.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a84e0582858d841d54355246ddfcbd1fce3179f185da7470f41ce39d001ee1af", size = 3979960 }, + { url = "https://files.pythonhosted.org/packages/4d/04/167f096386120f692cc4ca02f75a17b961858997a95e67a3cb6a7bbd6b53/shapely-2.1.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:dc3487447a43d42adcdf52d7ac73804f2312cbfa5d433a7d2c506dcab0033dfd", size = 4142851 }, + { url = "https://files.pythonhosted.org/packages/48/74/fb402c5a6235d1c65a97348b48cdedb75fb19eca2b1d66d04969fc1c6091/shapely-2.1.2-cp313-cp313-win32.whl", hash = "sha256:9c3a3c648aedc9f99c09263b39f2d8252f199cb3ac154fadc173283d7d111350", size = 1541890 }, + { url = "https://files.pythonhosted.org/packages/41/47/3647fe7ad990af60ad98b889657a976042c9988c2807cf322a9d6685f462/shapely-2.1.2-cp313-cp313-win_amd64.whl", hash = "sha256:ca2591bff6645c216695bdf1614fca9c82ea1144d4a7591a466fef64f28f0715", size = 1722151 }, + { url = "https://files.pythonhosted.org/packages/3c/49/63953754faa51ffe7d8189bfbe9ca34def29f8c0e34c67cbe2a2795f269d/shapely-2.1.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:2d93d23bdd2ed9dc157b46bc2f19b7da143ca8714464249bef6771c679d5ff40", size = 1834130 }, + { url = "https://files.pythonhosted.org/packages/7f/ee/dce001c1984052970ff60eb4727164892fb2d08052c575042a47f5a9e88f/shapely-2.1.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:01d0d304b25634d60bd7cf291828119ab55a3bab87dc4af1e44b07fb225f188b", size = 1642802 }, + { url = "https://files.pythonhosted.org/packages/da/e7/fc4e9a19929522877fa602f705706b96e78376afb7fad09cad5b9af1553c/shapely-2.1.2-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:8d8382dd120d64b03698b7298b89611a6ea6f55ada9d39942838b79c9bc89801", size = 3018460 }, + { url = "https://files.pythonhosted.org/packages/a1/18/7519a25db21847b525696883ddc8e6a0ecaa36159ea88e0fef11466384d0/shapely-2.1.2-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:19efa3611eef966e776183e338b2d7ea43569ae99ab34f8d17c2c054d3205cc0", size = 3095223 }, + { url = "https://files.pythonhosted.org/packages/48/de/b59a620b1f3a129c3fecc2737104a0a7e04e79335bd3b0a1f1609744cf17/shapely-2.1.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:346ec0c1a0fcd32f57f00e4134d1200e14bf3f5ae12af87ba83ca275c502498c", size = 4030760 }, + { url = "https://files.pythonhosted.org/packages/96/b3/c6655ee7232b417562bae192ae0d3ceaadb1cc0ffc2088a2ddf415456cc2/shapely-2.1.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:6305993a35989391bd3476ee538a5c9a845861462327efe00dd11a5c8c709a99", size = 4170078 }, + { url = "https://files.pythonhosted.org/packages/a0/8e/605c76808d73503c9333af8f6cbe7e1354d2d238bda5f88eea36bfe0f42a/shapely-2.1.2-cp313-cp313t-win32.whl", hash = "sha256:c8876673449f3401f278c86eb33224c5764582f72b653a415d0e6672fde887bf", size = 1559178 }, + { url = "https://files.pythonhosted.org/packages/36/f7/d317eb232352a1f1444d11002d477e54514a4a6045536d49d0c59783c0da/shapely-2.1.2-cp313-cp313t-win_amd64.whl", hash = "sha256:4a44bc62a10d84c11a7a3d7c1c4fe857f7477c3506e24c9062da0db0ae0c449c", size = 1739756 }, + { url = "https://files.pythonhosted.org/packages/fc/c4/3ce4c2d9b6aabd27d26ec988f08cb877ba9e6e96086eff81bfea93e688c7/shapely-2.1.2-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:9a522f460d28e2bf4e12396240a5fc1518788b2fcd73535166d748399ef0c223", size = 1831290 }, + { url = "https://files.pythonhosted.org/packages/17/b9/f6ab8918fc15429f79cb04afa9f9913546212d7fb5e5196132a2af46676b/shapely-2.1.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1ff629e00818033b8d71139565527ced7d776c269a49bd78c9df84e8f852190c", size = 1641463 }, + { url = "https://files.pythonhosted.org/packages/a5/57/91d59ae525ca641e7ac5551c04c9503aee6f29b92b392f31790fcb1a4358/shapely-2.1.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f67b34271dedc3c653eba4e3d7111aa421d5be9b4c4c7d38d30907f796cb30df", size = 2970145 }, + { url = "https://files.pythonhosted.org/packages/8a/cb/4948be52ee1da6927831ab59e10d4c29baa2a714f599f1f0d1bc747f5777/shapely-2.1.2-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:21952dc00df38a2c28375659b07a3979d22641aeb104751e769c3ee825aadecf", size = 3073806 }, + { url = "https://files.pythonhosted.org/packages/03/83/f768a54af775eb41ef2e7bec8a0a0dbe7d2431c3e78c0a8bdba7ab17e446/shapely-2.1.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:1f2f33f486777456586948e333a56ae21f35ae273be99255a191f5c1fa302eb4", size = 3980803 }, + { url = "https://files.pythonhosted.org/packages/9f/cb/559c7c195807c91c79d38a1f6901384a2878a76fbdf3f1048893a9b7534d/shapely-2.1.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:cf831a13e0d5a7eb519e96f58ec26e049b1fad411fc6fc23b162a7ce04d9cffc", size = 4133301 }, + { url = "https://files.pythonhosted.org/packages/80/cd/60d5ae203241c53ef3abd2ef27c6800e21afd6c94e39db5315ea0cbafb4a/shapely-2.1.2-cp314-cp314-win32.whl", hash = "sha256:61edcd8d0d17dd99075d320a1dd39c0cb9616f7572f10ef91b4b5b00c4aeb566", size = 1583247 }, + { url = "https://files.pythonhosted.org/packages/74/d4/135684f342e909330e50d31d441ace06bf83c7dc0777e11043f99167b123/shapely-2.1.2-cp314-cp314-win_amd64.whl", hash = "sha256:a444e7afccdb0999e203b976adb37ea633725333e5b119ad40b1ca291ecf311c", size = 1773019 }, + { url = "https://files.pythonhosted.org/packages/a3/05/a44f3f9f695fa3ada22786dc9da33c933da1cbc4bfe876fe3a100bafe263/shapely-2.1.2-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:5ebe3f84c6112ad3d4632b1fd2290665aa75d4cef5f6c5d77c4c95b324527c6a", size = 1834137 }, + { url = "https://files.pythonhosted.org/packages/52/7e/4d57db45bf314573427b0a70dfca15d912d108e6023f623947fa69f39b72/shapely-2.1.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:5860eb9f00a1d49ebb14e881f5caf6c2cf472c7fd38bd7f253bbd34f934eb076", size = 1642884 }, + { url = "https://files.pythonhosted.org/packages/5a/27/4e29c0a55d6d14ad7422bf86995d7ff3f54af0eba59617eb95caf84b9680/shapely-2.1.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b705c99c76695702656327b819c9660768ec33f5ce01fa32b2af62b56ba400a1", size = 3018320 }, + { url = "https://files.pythonhosted.org/packages/9f/bb/992e6a3c463f4d29d4cd6ab8963b75b1b1040199edbd72beada4af46bde5/shapely-2.1.2-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:a1fd0ea855b2cf7c9cddaf25543e914dd75af9de08785f20ca3085f2c9ca60b0", size = 3094931 }, + { url = "https://files.pythonhosted.org/packages/9c/16/82e65e21070e473f0ed6451224ed9fa0be85033d17e0c6e7213a12f59d12/shapely-2.1.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:df90e2db118c3671a0754f38e36802db75fe0920d211a27481daf50a711fdf26", size = 4030406 }, + { url = "https://files.pythonhosted.org/packages/7c/75/c24ed871c576d7e2b64b04b1fe3d075157f6eb54e59670d3f5ffb36e25c7/shapely-2.1.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:361b6d45030b4ac64ddd0a26046906c8202eb60d0f9f53085f5179f1d23021a0", size = 4169511 }, + { url = "https://files.pythonhosted.org/packages/b1/f7/b3d1d6d18ebf55236eec1c681ce5e665742aab3c0b7b232720a7d43df7b6/shapely-2.1.2-cp314-cp314t-win32.whl", hash = "sha256:b54df60f1fbdecc8ebc2c5b11870461a6417b3d617f555e5033f1505d36e5735", size = 1602607 }, + { url = "https://files.pythonhosted.org/packages/9a/f6/f09272a71976dfc138129b8faf435d064a811ae2f708cb147dccdf7aacdb/shapely-2.1.2-cp314-cp314t-win_amd64.whl", hash = "sha256:0036ac886e0923417932c2e6369b6c52e38e0ff5d9120b90eef5cd9a5fc5cae9", size = 1796682 }, +] + +[[package]] +name = "simplekml" +version = "1.3.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/15/e4/c333a93b7e3346437ad1ff42b8e362b853eb405ad6243ab6163f9af2a460/simplekml-1.3.6.tar.gz", hash = "sha256:cda687be2754395fcab664e908ebf589facd41e8436d233d2be37a69efb1c536", size = 52999 } + +[[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 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050 }, +] + +[[package]] +name = "stack-data" +version = "0.6.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "asttokens" }, + { name = "executing" }, + { name = "pure-eval" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/28/e3/55dcc2cfbc3ca9c29519eb6884dd1415ecb53b0e934862d3559ddcb7e20b/stack_data-0.6.3.tar.gz", hash = "sha256:836a778de4fec4dcd1dcd89ed8abff8a221f58308462e1c4aa2a3cf30148f0b9", size = 44707 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f1/7b/ce1eafaf1a76852e2ec9b22edecf1daa58175c090266e9f6c64afcd81d91/stack_data-0.6.3-py3-none-any.whl", hash = "sha256:d5558e0c25a4cb0853cddad3d77da9891a08cb85dd9f9f91b9f8cd66e511e695", size = 24521 }, +] + +[[package]] +name = "tomli" +version = "2.4.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/22/de/48c59722572767841493b26183a0d1cc411d54fd759c5607c4590b6563a6/tomli-2.4.1.tar.gz", hash = "sha256:7c7e1a961a0b2f2472c1ac5b69affa0ae1132c39adcb67aba98568702b9cc23f", size = 17543 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f4/11/db3d5885d8528263d8adc260bb2d28ebf1270b96e98f0e0268d32b8d9900/tomli-2.4.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f8f0fc26ec2cc2b965b7a3b87cd19c5c6b8c5e5f436b984e85f486d652285c30", size = 154704 }, + { url = "https://files.pythonhosted.org/packages/6d/f7/675db52c7e46064a9aa928885a9b20f4124ecb9bc2e1ce74c9106648d202/tomli-2.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4ab97e64ccda8756376892c53a72bd1f964e519c77236368527f758fbc36a53a", size = 149454 }, + { url = "https://files.pythonhosted.org/packages/61/71/81c50943cf953efa35bce7646caab3cf457a7d8c030b27cfb40d7235f9ee/tomli-2.4.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:96481a5786729fd470164b47cdb3e0e58062a496f455ee41b4403be77cb5a076", size = 237561 }, + { url = "https://files.pythonhosted.org/packages/48/c1/f41d9cb618acccca7df82aaf682f9b49013c9397212cb9f53219e3abac37/tomli-2.4.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5a881ab208c0baf688221f8cecc5401bd291d67e38a1ac884d6736cbcd8247e9", size = 243824 }, + { url = "https://files.pythonhosted.org/packages/22/e4/5a816ecdd1f8ca51fb756ef684b90f2780afc52fc67f987e3c61d800a46d/tomli-2.4.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:47149d5bd38761ac8be13a84864bf0b7b70bc051806bc3669ab1cbc56216b23c", size = 242227 }, + { url = "https://files.pythonhosted.org/packages/6b/49/2b2a0ef529aa6eec245d25f0c703e020a73955ad7edf73e7f54ddc608aa5/tomli-2.4.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ec9bfaf3ad2df51ace80688143a6a4ebc09a248f6ff781a9945e51937008fcbc", size = 247859 }, + { url = "https://files.pythonhosted.org/packages/83/bd/6c1a630eaca337e1e78c5903104f831bda934c426f9231429396ce3c3467/tomli-2.4.1-cp311-cp311-win32.whl", hash = "sha256:ff2983983d34813c1aeb0fa89091e76c3a22889ee83ab27c5eeb45100560c049", size = 97204 }, + { url = "https://files.pythonhosted.org/packages/42/59/71461df1a885647e10b6bb7802d0b8e66480c61f3f43079e0dcd315b3954/tomli-2.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:5ee18d9ebdb417e384b58fe414e8d6af9f4e7a0ae761519fb50f721de398dd4e", size = 108084 }, + { url = "https://files.pythonhosted.org/packages/b8/83/dceca96142499c069475b790e7913b1044c1a4337e700751f48ed723f883/tomli-2.4.1-cp311-cp311-win_arm64.whl", hash = "sha256:c2541745709bad0264b7d4705ad453b76ccd191e64aa6f0fc66b69a293a45ece", size = 95285 }, + { url = "https://files.pythonhosted.org/packages/c1/ba/42f134a3fe2b370f555f44b1d72feebb94debcab01676bf918d0cb70e9aa/tomli-2.4.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c742f741d58a28940ce01d58f0ab2ea3ced8b12402f162f4d534dfe18ba1cd6a", size = 155924 }, + { url = "https://files.pythonhosted.org/packages/dc/c7/62d7a17c26487ade21c5422b646110f2162f1fcc95980ef7f63e73c68f14/tomli-2.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7f86fd587c4ed9dd76f318225e7d9b29cfc5a9d43de44e5754db8d1128487085", size = 150018 }, + { url = "https://files.pythonhosted.org/packages/5c/05/79d13d7c15f13bdef410bdd49a6485b1c37d28968314eabee452c22a7fda/tomli-2.4.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ff18e6a727ee0ab0388507b89d1bc6a22b138d1e2fa56d1ad494586d61d2eae9", size = 244948 }, + { url = "https://files.pythonhosted.org/packages/10/90/d62ce007a1c80d0b2c93e02cab211224756240884751b94ca72df8a875ca/tomli-2.4.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:136443dbd7e1dee43c68ac2694fde36b2849865fa258d39bf822c10e8068eac5", size = 253341 }, + { url = "https://files.pythonhosted.org/packages/1a/7e/caf6496d60152ad4ed09282c1885cca4eea150bfd007da84aea07bcc0a3e/tomli-2.4.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5e262d41726bc187e69af7825504c933b6794dc3fbd5945e41a79bb14c31f585", size = 248159 }, + { url = "https://files.pythonhosted.org/packages/99/e7/c6f69c3120de34bbd882c6fba7975f3d7a746e9218e56ab46a1bc4b42552/tomli-2.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5cb41aa38891e073ee49d55fbc7839cfdb2bc0e600add13874d048c94aadddd1", size = 253290 }, + { url = "https://files.pythonhosted.org/packages/d6/2f/4a3c322f22c5c66c4b836ec58211641a4067364f5dcdd7b974b4c5da300c/tomli-2.4.1-cp312-cp312-win32.whl", hash = "sha256:da25dc3563bff5965356133435b757a795a17b17d01dbc0f42fb32447ddfd917", size = 98141 }, + { url = "https://files.pythonhosted.org/packages/24/22/4daacd05391b92c55759d55eaee21e1dfaea86ce5c571f10083360adf534/tomli-2.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:52c8ef851d9a240f11a88c003eacb03c31fc1c9c4ec64a99a0f922b93874fda9", size = 108847 }, + { url = "https://files.pythonhosted.org/packages/68/fd/70e768887666ddd9e9f5d85129e84910f2db2796f9096aa02b721a53098d/tomli-2.4.1-cp312-cp312-win_arm64.whl", hash = "sha256:f758f1b9299d059cc3f6546ae2af89670cb1c4d48ea29c3cacc4fe7de3058257", size = 95088 }, + { url = "https://files.pythonhosted.org/packages/07/06/b823a7e818c756d9a7123ba2cda7d07bc2dd32835648d1a7b7b7a05d848d/tomli-2.4.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:36d2bd2ad5fb9eaddba5226aa02c8ec3fa4f192631e347b3ed28186d43be6b54", size = 155866 }, + { url = "https://files.pythonhosted.org/packages/14/6f/12645cf7f08e1a20c7eb8c297c6f11d31c1b50f316a7e7e1e1de6e2e7b7e/tomli-2.4.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:eb0dc4e38e6a1fd579e5d50369aa2e10acfc9cace504579b2faabb478e76941a", size = 149887 }, + { url = "https://files.pythonhosted.org/packages/5c/e0/90637574e5e7212c09099c67ad349b04ec4d6020324539297b634a0192b0/tomli-2.4.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c7f2c7f2b9ca6bdeef8f0fa897f8e05085923eb091721675170254cbc5b02897", size = 243704 }, + { url = "https://files.pythonhosted.org/packages/10/8f/d3ddb16c5a4befdf31a23307f72828686ab2096f068eaf56631e136c1fdd/tomli-2.4.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f3c6818a1a86dd6dca7ddcaaf76947d5ba31aecc28cb1b67009a5877c9a64f3f", size = 251628 }, + { url = "https://files.pythonhosted.org/packages/e3/f1/dbeeb9116715abee2485bf0a12d07a8f31af94d71608c171c45f64c0469d/tomli-2.4.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d312ef37c91508b0ab2cee7da26ec0b3ed2f03ce12bd87a588d771ae15dcf82d", size = 247180 }, + { url = "https://files.pythonhosted.org/packages/d3/74/16336ffd19ed4da28a70959f92f506233bd7cfc2332b20bdb01591e8b1d1/tomli-2.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:51529d40e3ca50046d7606fa99ce3956a617f9b36380da3b7f0dd3dd28e68cb5", size = 251674 }, + { url = "https://files.pythonhosted.org/packages/16/f9/229fa3434c590ddf6c0aa9af64d3af4b752540686cace29e6281e3458469/tomli-2.4.1-cp313-cp313-win32.whl", hash = "sha256:2190f2e9dd7508d2a90ded5ed369255980a1bcdd58e52f7fe24b8162bf9fedbd", size = 97976 }, + { url = "https://files.pythonhosted.org/packages/6a/1e/71dfd96bcc1c775420cb8befe7a9d35f2e5b1309798f009dca17b7708c1e/tomli-2.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:8d65a2fbf9d2f8352685bc1364177ee3923d6baf5e7f43ea4959d7d8bc326a36", size = 108755 }, + { url = "https://files.pythonhosted.org/packages/83/7a/d34f422a021d62420b78f5c538e5b102f62bea616d1d75a13f0a88acb04a/tomli-2.4.1-cp313-cp313-win_arm64.whl", hash = "sha256:4b605484e43cdc43f0954ddae319fb75f04cc10dd80d830540060ee7cd0243cd", size = 95265 }, + { url = "https://files.pythonhosted.org/packages/3c/fb/9a5c8d27dbab540869f7c1f8eb0abb3244189ce780ba9cd73f3770662072/tomli-2.4.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:fd0409a3653af6c147209d267a0e4243f0ae46b011aa978b1080359fddc9b6cf", size = 155726 }, + { url = "https://files.pythonhosted.org/packages/62/05/d2f816630cc771ad836af54f5001f47a6f611d2d39535364f148b6a92d6b/tomli-2.4.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:a120733b01c45e9a0c34aeef92bf0cf1d56cfe81ed9d47d562f9ed591a9828ac", size = 149859 }, + { url = "https://files.pythonhosted.org/packages/ce/48/66341bdb858ad9bd0ceab5a86f90eddab127cf8b046418009f2125630ecb/tomli-2.4.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:559db847dc486944896521f68d8190be1c9e719fced785720d2216fe7022b662", size = 244713 }, + { url = "https://files.pythonhosted.org/packages/df/6d/c5fad00d82b3c7a3ab6189bd4b10e60466f22cfe8a08a9394185c8a8111c/tomli-2.4.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:01f520d4f53ef97964a240a035ec2a869fe1a37dde002b57ebc4417a27ccd853", size = 252084 }, + { url = "https://files.pythonhosted.org/packages/00/71/3a69e86f3eafe8c7a59d008d245888051005bd657760e96d5fbfb0b740c2/tomli-2.4.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7f94b27a62cfad8496c8d2513e1a222dd446f095fca8987fceef261225538a15", size = 247973 }, + { url = "https://files.pythonhosted.org/packages/67/50/361e986652847fec4bd5e4a0208752fbe64689c603c7ae5ea7cb16b1c0ca/tomli-2.4.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:ede3e6487c5ef5d28634ba3f31f989030ad6af71edfb0055cbbd14189ff240ba", size = 256223 }, + { url = "https://files.pythonhosted.org/packages/8c/9a/b4173689a9203472e5467217e0154b00e260621caa227b6fa01feab16998/tomli-2.4.1-cp314-cp314-win32.whl", hash = "sha256:3d48a93ee1c9b79c04bb38772ee1b64dcf18ff43085896ea460ca8dec96f35f6", size = 98973 }, + { url = "https://files.pythonhosted.org/packages/14/58/640ac93bf230cd27d002462c9af0d837779f8773bc03dee06b5835208214/tomli-2.4.1-cp314-cp314-win_amd64.whl", hash = "sha256:88dceee75c2c63af144e456745e10101eb67361050196b0b6af5d717254dddf7", size = 109082 }, + { url = "https://files.pythonhosted.org/packages/d5/2f/702d5e05b227401c1068f0d386d79a589bb12bf64c3d2c72ce0631e3bc49/tomli-2.4.1-cp314-cp314-win_arm64.whl", hash = "sha256:b8c198f8c1805dc42708689ed6864951fd2494f924149d3e4bce7710f8eb5232", size = 96490 }, + { url = "https://files.pythonhosted.org/packages/45/4b/b877b05c8ba62927d9865dd980e34a755de541eb65fffba52b4cc495d4d2/tomli-2.4.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:d4d8fe59808a54658fcc0160ecfb1b30f9089906c50b23bcb4c69eddc19ec2b4", size = 164263 }, + { url = "https://files.pythonhosted.org/packages/24/79/6ab420d37a270b89f7195dec5448f79400d9e9c1826df982f3f8e97b24fd/tomli-2.4.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7008df2e7655c495dd12d2a4ad038ff878d4ca4b81fccaf82b714e07eae4402c", size = 160736 }, + { url = "https://files.pythonhosted.org/packages/02/e0/3630057d8eb170310785723ed5adcdfb7d50cb7e6455f85ba8a3deed642b/tomli-2.4.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1d8591993e228b0c930c4bb0db464bdad97b3289fb981255d6c9a41aedc84b2d", size = 270717 }, + { url = "https://files.pythonhosted.org/packages/7a/b4/1613716072e544d1a7891f548d8f9ec6ce2faf42ca65acae01d76ea06bb0/tomli-2.4.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:734e20b57ba95624ecf1841e72b53f6e186355e216e5412de414e3c51e5e3c41", size = 278461 }, + { url = "https://files.pythonhosted.org/packages/05/38/30f541baf6a3f6df77b3df16b01ba319221389e2da59427e221ef417ac0c/tomli-2.4.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8a650c2dbafa08d42e51ba0b62740dae4ecb9338eefa093aa5c78ceb546fcd5c", size = 274855 }, + { url = "https://files.pythonhosted.org/packages/77/a3/ec9dd4fd2c38e98de34223b995a3b34813e6bdadf86c75314c928350ed14/tomli-2.4.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:504aa796fe0569bb43171066009ead363de03675276d2d121ac1a4572397870f", size = 283144 }, + { url = "https://files.pythonhosted.org/packages/ef/be/605a6261cac79fba2ec0c9827e986e00323a1945700969b8ee0b30d85453/tomli-2.4.1-cp314-cp314t-win32.whl", hash = "sha256:b1d22e6e9387bf4739fbe23bfa80e93f6b0373a7f1b96c6227c32bef95a4d7a8", size = 108683 }, + { url = "https://files.pythonhosted.org/packages/12/64/da524626d3b9cc40c168a13da8335fe1c51be12c0a63685cc6db7308daae/tomli-2.4.1-cp314-cp314t-win_amd64.whl", hash = "sha256:2c1c351919aca02858f740c6d33adea0c5deea37f9ecca1cc1ef9e884a619d26", size = 121196 }, + { url = "https://files.pythonhosted.org/packages/5a/cd/e80b62269fc78fc36c9af5a6b89c835baa8af28ff5ad28c7028d60860320/tomli-2.4.1-cp314-cp314t-win_arm64.whl", hash = "sha256:eab21f45c7f66c13f2a9e0e1535309cee140182a9cdae1e041d02e47291e8396", size = 100393 }, + { url = "https://files.pythonhosted.org/packages/7b/61/cceae43728b7de99d9b847560c262873a1f6c98202171fd5ed62640b494b/tomli-2.4.1-py3-none-any.whl", hash = "sha256:0d85819802132122da43cb86656f8d1f8c6587d54ae7dcaf30e90533028b49fe", size = 14583 }, +] + +[[package]] +name = "traitlets" +version = "5.15.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/57/a9/a2584b8313b89f94869ddb3c4074617a691de1812a614d2d50e32ca5a7a6/traitlets-5.15.1.tar.gz", hash = "sha256:7b1c07854fe25acb39e009bae49f11b79ff6cbb2f27999104e9110e7a6b53722", size = 163344 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/96/8d/1080ee4c231f361b6ce4470d556c8c435b67c7e0753aaa641497ee92f88b/traitlets-5.15.1-py3-none-any.whl", hash = "sha256:770a53705f84b81ac107e83a1b3328ff2dae16094d8fc3cfc004e4b22dfd8e92", size = 85858 }, +] + +[[package]] +name = "transformations" +version = "2025.1.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.11'", +] +dependencies = [ + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/6b/ba/678cbd4f558ec587e6ced36206dec0649d4c383dfcdf76c0cdd09d889e30/transformations-2025.1.1.tar.gz", hash = "sha256:b8411a456cd506e4b77cdac884b217836125840e2f1400247b5bc02c46d1b333", size = 48065 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4e/dc/679989c58a3646740b27ab6dcce4dd3d6991ddd81f2fed7b145c57e0c6c3/transformations-2025.1.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5eb808d255c02fba6cfdbc92b7c706432db76b7e55e0cfe921bddad95e1d4285", size = 58017 }, + { url = "https://files.pythonhosted.org/packages/b6/c9/348fa3320c5cd3a403eedaebc6750c20f45f04b39d1eb6da8d9e339619b8/transformations-2025.1.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:cbc758a858185b38022722ab6f5bb39369b74e69a8e7a861894870269241064f", size = 54316 }, + { url = "https://files.pythonhosted.org/packages/7e/46/8d70ff706699b44107f1c649ca4af7c9665da313b706cf5f4bb1ec4db1f4/transformations-2025.1.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2ed30be8eed56680dd1405117dfae58f2ebba99cff1333503b8cdac46c85f976", size = 140582 }, + { url = "https://files.pythonhosted.org/packages/f0/62/3f0f3ac6df0b68dadb354e27e3a6a010214ee3026a9ec8996b499c9dd4d8/transformations-2025.1.1-cp310-cp310-win32.whl", hash = "sha256:b50e7bff1d3a776d71b69a3e38c7a776666756011bf5a071739c84b93c481094", size = 53712 }, + { url = "https://files.pythonhosted.org/packages/f6/cd/16986f29515dd295abb570120c9526ba3958f252cb1241d8632bfcf2a358/transformations-2025.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:1af88add05b44c752379c4ff0649a17d0853dea65ca20f162661bcc3488ab48c", size = 58532 }, + { url = "https://files.pythonhosted.org/packages/d4/48/eaf3f8e4ef8f65fd35bae43d0e4bb9da939032d1d0ca5af4b0174302a0e4/transformations-2025.1.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ba5810bbcc403a6fdb0ad55f2d1654ae434ad5e0ed6411c4fd5ee95d3c24a785", size = 58016 }, + { url = "https://files.pythonhosted.org/packages/99/2d/1af87336b9028430abb2c6a5243761f54c69c718995d25d3f2eeb1b400ff/transformations-2025.1.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ee99aee395cbc033e97f3df6c74e6f65e77ea29399e6c2265a4aaada22bc1af3", size = 54307 }, + { url = "https://files.pythonhosted.org/packages/24/43/c12e5e5f4648257be1a1c2deacefffccff05b0cd334f7108dc60ee34c87b/transformations-2025.1.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9ce9fa1663d16126cfd746ca79dfc484db531cd68b1ee99dfd5960f65b82331a", size = 141600 }, + { url = "https://files.pythonhosted.org/packages/69/b5/254149892bbbdaa7da0cfc7858319fdd4bf73ef50ce5c38fffafb83dc902/transformations-2025.1.1-cp311-cp311-win32.whl", hash = "sha256:0480bf56c375070e374994586acfc7366a91a4ec2356e8111394212699a11c0a", size = 53712 }, + { url = "https://files.pythonhosted.org/packages/2d/42/687cacb4689dd25fddfa8b284d3a3f4d6f44915d039a5da2d0fe9f381d2f/transformations-2025.1.1-cp311-cp311-win_amd64.whl", hash = "sha256:6053d98f6e9d631df2ef467dcd534138c23532b425ca44cc57049197be8a2df0", size = 58536 }, + { url = "https://files.pythonhosted.org/packages/22/46/8602a4ded8f5ebf66d366848e8da90eb6d3f0cb5167b8c2657754e4de0e5/transformations-2025.1.1-cp311-cp311-win_arm64.whl", hash = "sha256:609ea776cbd25d81f83be08c6e19576a430b888dfda3f8a9b833c91fee624d23", size = 51550 }, + { url = "https://files.pythonhosted.org/packages/ed/23/7abfdc715b065868f77e1e03381e34d3c526b11a21882afc2d7e4bba9d7e/transformations-2025.1.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:96b9dae9b63d27878422de914630983de937de6699f5e0114c440e2d49bb28df", size = 58108 }, + { url = "https://files.pythonhosted.org/packages/b6/2c/2af560efb7e63ca92d6acf69d172fd714abf698b1331c5c2fc808801d509/transformations-2025.1.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0c0f7b8980053c0e4f93e33a6899adbef85d6473d4b0f004a0addcfd1c310461", size = 54274 }, + { url = "https://files.pythonhosted.org/packages/7d/3b/b436ef8290dfbd82d31f21ac91a6f792ebc2085315fccaba3859a2f880b1/transformations-2025.1.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c31602afe8480d8b1ec3ef57fad91b655f5aaf13d6913e04a9d16200ebbd0b9d", size = 145584 }, + { url = "https://files.pythonhosted.org/packages/02/9e/da0ae005d9ef81ae0ea92cdb28597cc563a90b74f9dea3e90065021e6c91/transformations-2025.1.1-cp312-cp312-win32.whl", hash = "sha256:fbe073d36a4f5132778aae0f837c9fa1797dc0b5f86e67efacc9be34cf886222", size = 53778 }, + { url = "https://files.pythonhosted.org/packages/6a/72/2affe2931de75bb4c8ddee0c3b3c6fd60988b66ab4f096c1e9a59f508a71/transformations-2025.1.1-cp312-cp312-win_amd64.whl", hash = "sha256:182c883f06ee9cdaa57b33ecadd694b519e682d5c050026613f260716250fbfc", size = 58817 }, + { url = "https://files.pythonhosted.org/packages/c4/f8/eeab0b9e542a8870968a4b8d6a5f9fde1eaf5925972d9050c962c2437bf4/transformations-2025.1.1-cp312-cp312-win_arm64.whl", hash = "sha256:90a494108ff1b37ba3505492098170006559aca453b7715ae41983b325f731f4", size = 51709 }, + { url = "https://files.pythonhosted.org/packages/04/59/8d3526b3a19da156767088c9bb5c5872cdeedece31a9f829a32ad429a4fc/transformations-2025.1.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:264c71652cb98c35bfa0293fb2e0d3a811a645f3ca4011756321baaa6953b287", size = 58115 }, + { url = "https://files.pythonhosted.org/packages/43/c3/968c56642c3f53c89d4aeb816f29e6ac3f8cf05656904116c176df57c19c/transformations-2025.1.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:9e68e475256cfe03e3fa360693d6a39495b2b67d86033640504067ce8febf29b", size = 54276 }, + { url = "https://files.pythonhosted.org/packages/be/16/aa7213cb432ec16a2c91138522616cf4971502e6c385dd04cdfd1d467c52/transformations-2025.1.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:abdba200ec5abfac55fab25c5ef399fd65357d296c304f1499c0af6b9442fc8a", size = 145562 }, + { url = "https://files.pythonhosted.org/packages/8f/b7/ad2c0c9f9ec54ce649c50de8098d81a4786d2cf35703f7da9f3ad5df12a5/transformations-2025.1.1-cp313-cp313-win32.whl", hash = "sha256:26950745a1d7fcdbdb2f1ba217496a2ac4c1eeb4cacbd0e866fa48e09c9d01ee", size = 53779 }, + { url = "https://files.pythonhosted.org/packages/0b/06/be24e40b458cf3d276c6aa39b36bf7cb16ee50ca4d6922b9d2f0bbea5db5/transformations-2025.1.1-cp313-cp313-win_amd64.whl", hash = "sha256:bffdd9c419cd395af9e1de5a642099dfc9ec0cb2b1d9d87c0421271083603e7c", size = 58818 }, + { url = "https://files.pythonhosted.org/packages/cb/c3/b6d0d31c7fd8c702e6222d62d02f32db9228fccc3908e4ee46be4861edd0/transformations-2025.1.1-cp313-cp313-win_arm64.whl", hash = "sha256:4a44eec2c7ae4058316ae75fbfd3c3ab8d15489d0bbeb753f6a4392283378d94", size = 51708 }, +] + +[[package]] +name = "transformations" +version = "2026.1.18" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version == '3.12.*'", + "python_full_version == '3.11.*'", + "python_full_version >= '3.13'", +] +dependencies = [ + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.11.*'" }, + { name = "numpy", version = "2.5.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/4a/48/be4f992f75aacc805442d28e2a237e085bc54dc88fc57fc13736a5c24bbe/transformations-2026.1.18.tar.gz", hash = "sha256:d7bdb5f7753b520facb42df6b21c6756126dd89a90700867a9162bebc6095c73", size = 48524 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/29/29/1b49f789a7e53b71ffaca395081e4d8d324ed454666ecdb9a47d502e6e6f/transformations-2026.1.18-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b3ac3d84d8543860bcf428feeb142b084fc372c640253ec448a7322f27a06996", size = 57696 }, + { url = "https://files.pythonhosted.org/packages/06/27/c55edbe588e89f3b75ed0b4ce2655e6f3e9a912f9b740fe3083f61aebcdb/transformations-2026.1.18-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:cfba16f6c9ec4090bee358af7ac131af19d6fb68843a0e4f59960016940e23ec", size = 54727 }, + { url = "https://files.pythonhosted.org/packages/5f/5f/68acb365814c371faa841afc196b025ba3156e8bf43ba8586cca299813f7/transformations-2026.1.18-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:612d74d463f6c830a5e293cbd432b1077ba443f529877594a58b359d39c32050", size = 139220 }, + { url = "https://files.pythonhosted.org/packages/b4/67/769f7d64d981b704044570155709ce9dc68b4d69683c099b798fada3e740/transformations-2026.1.18-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:af5147e613a053c5abbc49f4f1f168e2e181b81d6c8249ff9d4b03b856361eb1", size = 144451 }, + { url = "https://files.pythonhosted.org/packages/50/7c/1d044f0e9ce59c9ffeb0e3835f37dc91fb34f9d93a8a4ad24a5181cb12f7/transformations-2026.1.18-cp311-cp311-win32.whl", hash = "sha256:176bfaa55570a04e260847e37c007fb5b2dc78633b18e20bfa212d46b1f8cb1f", size = 53631 }, + { url = "https://files.pythonhosted.org/packages/ce/55/740fe6e04b13fe16c316440bce48ebd3707fd5c2dfddef4c16561f8aec2a/transformations-2026.1.18-cp311-cp311-win_amd64.whl", hash = "sha256:cfca85bda82df8403437d643a4dea7886e0cb6a5fe1b25c35d000c4680161eed", size = 59185 }, + { url = "https://files.pythonhosted.org/packages/45/81/ac07f93cdfc800c692530c44699390fca21a3fe6bdc73203c84e3f573ceb/transformations-2026.1.18-cp311-cp311-win_arm64.whl", hash = "sha256:9ef0264227f5139c164329df5fc03566b1948b5b8640f8c03801d6c691d0af92", size = 51843 }, + { url = "https://files.pythonhosted.org/packages/a3/58/f68661320cc1835c208baf6bbaff7398dfc0b6bac3d66d01fa88ee49c1d2/transformations-2026.1.18-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:ea335cc8097d7b453dedf9db5fccd71827c6748c814f5dcad1db03fa5bae4029", size = 57834 }, + { url = "https://files.pythonhosted.org/packages/10/9c/47e489665240f48540a1db1e6357f1f26ce289ef0620cf46476a187b1129/transformations-2026.1.18-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5cb91276a23fabd8b0f7fc362d5aa63d41cdc50dbf38da34bdc6dfb1f3bc7462", size = 54658 }, + { url = "https://files.pythonhosted.org/packages/62/f8/93d8214875bce0afd25401b2e39d2912cc7e4b3dcaa504ad61f1d459e3b0/transformations-2026.1.18-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5fe34e7119e07566e01b91cf9f19c57d2c0cc9330e2cdd49a1a513ca8a53842f", size = 143393 }, + { url = "https://files.pythonhosted.org/packages/4a/d1/c140c92c8e159806d8ffd5c1c3f9e6dab562ff9b2ce7fc007266b98c9b68/transformations-2026.1.18-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dc2e52cb0c0f4b838c7b6da964fb18f43058abfe767c6c3f6a465b9fc0b3dd12", size = 149295 }, + { url = "https://files.pythonhosted.org/packages/c3/af/b060381ba35ad84490934e93a499c668c6123c02f75e1763c859b0c9e4db/transformations-2026.1.18-cp312-cp312-win32.whl", hash = "sha256:374c54c0ffb61d34b811134322472db49bad2216ccb31ee175133e0f46d97de9", size = 53913 }, + { url = "https://files.pythonhosted.org/packages/99/d0/0ef42e50df0e44e88866a2694b4712375055b1230945038b9532a172e073/transformations-2026.1.18-cp312-cp312-win_amd64.whl", hash = "sha256:91237452998cacbcf04b243482f0de3be0899acaf5e7ca18831c0a9e1ad0f221", size = 59367 }, + { url = "https://files.pythonhosted.org/packages/2e/26/e0b0da1a26cdf326442af70c709f00779ce34f37c496842bd3fcc44896cb/transformations-2026.1.18-cp312-cp312-win_arm64.whl", hash = "sha256:da26c1740844dc6b58c3ad8a0e03b36244ac924423c25fc10387dcb2891b9b44", size = 52006 }, + { url = "https://files.pythonhosted.org/packages/33/70/a3aed6a6355435cb8ea826edba647bf80daa725a89ec679baa0364df1a6d/transformations-2026.1.18-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b51fadf3c8cd0ef9b01bfce7a72db590b58e2a6222cf137f69d98d6059aba6a0", size = 57838 }, + { url = "https://files.pythonhosted.org/packages/9b/a7/bdc6e9aa53be0dec78133650d83b84bf30319dc80ce51c1381b46a819bfe/transformations-2026.1.18-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:b768e73e68245a7478537bcaefaf7338cdf2ed804a6c32e7462269cc5342c0b6", size = 54657 }, + { url = "https://files.pythonhosted.org/packages/b9/66/66da44457cdbd7ccccb614847cf42e02b6196986faf7452770b0f5924cf9/transformations-2026.1.18-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:db2304ce26c1c928b7c7fc818902e4ef1ce4326be44cdefcee37628954b72cad", size = 143416 }, + { url = "https://files.pythonhosted.org/packages/a8/9e/db2e1a3a04a87a129035dc0ee4ec2fc84110cb06cd0b2260d5995224e3ac/transformations-2026.1.18-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d3c4a582888d06bc7b08dfc28259ca156b3dcdc5d3ccee367d02c3dd5f9b378e", size = 149370 }, + { url = "https://files.pythonhosted.org/packages/76/e6/fd237abe3905d85c107651ace841605019771adfade0a241c9f470524dcd/transformations-2026.1.18-cp313-cp313-win32.whl", hash = "sha256:9d0222368e7990bd338aa88292a0dad201db3f2a465e84f50a2c05eeb342c5f8", size = 53915 }, + { url = "https://files.pythonhosted.org/packages/43/fa/dfc8bffafe39fb52973c70da89f0623fd9a9e01c05b3dce22677d9f2ee9a/transformations-2026.1.18-cp313-cp313-win_amd64.whl", hash = "sha256:3ccf0fdb0d952e5e055815fb5a2831866bf65ecfd4d0b767a1ac3c205702091c", size = 59365 }, + { url = "https://files.pythonhosted.org/packages/79/89/6086f41f4f7365eb101d7c8fed7ae127475e4a578edb4de5f36c9bcd3562/transformations-2026.1.18-cp313-cp313-win_arm64.whl", hash = "sha256:a293f5bba9b3d6795ceb341b8234b1f9ff3531274bbea687d45280ca600f014e", size = 51998 }, + { url = "https://files.pythonhosted.org/packages/c1/6e/d78321d9d16aef583e6ee162b904121364cfe7eb96d6d66ed19a6a5bbac5/transformations-2026.1.18-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:e10bd48b1e3a1c857da0d06c847d235c0bb7399f62d2f9e6a022690712b1059e", size = 57911 }, + { url = "https://files.pythonhosted.org/packages/83/d5/96b224d3ae9e396da2006c9ceeb90304d35bacf1ab941e1d5eb5052eaa6a/transformations-2026.1.18-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:08e7b58e6ff25d941a52c04389c08832e62c027fbb8c53f3b87d3e0ce526647e", size = 54682 }, + { url = "https://files.pythonhosted.org/packages/58/91/308ccd53b2374ecb43bda4798c0ad28cac350391fe8214445e8e173cd190/transformations-2026.1.18-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0c5f0b85930957929fb8b5e5676c3aa54cb5754ce399f5cdd32b34f18a1fc05f", size = 143474 }, + { url = "https://files.pythonhosted.org/packages/99/32/d4df0cf7cffeefeb2bad6a8cd5bc337d6d41884c4b3a042a67a6b2dd3ba6/transformations-2026.1.18-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:960404eb5d62ee2cb5e03259938d618ee20891a57ae4bc87808b197ce9e81933", size = 149309 }, + { url = "https://files.pythonhosted.org/packages/79/d7/a0420228e998937845da9a361d4cf13715bd2db8429a8514781b334e62e9/transformations-2026.1.18-cp314-cp314-win32.whl", hash = "sha256:2ece1dec9aef2e354adab4c3443f226026bed0a207b771ba698fdbd5c1607484", size = 54810 }, + { url = "https://files.pythonhosted.org/packages/bb/79/ef05d9f0fec576d79db67388aec8e22e83798b4498aebbbe8c36bdd2e715/transformations-2026.1.18-cp314-cp314-win_amd64.whl", hash = "sha256:37dae6e61e1113cdf5e31e96e29665fb86ad8f0d1d95da7fdd5561756da5f56c", size = 60601 }, + { url = "https://files.pythonhosted.org/packages/ed/ae/67eba54cb4926d94434395b447478159a8666e2cf82b38e126eb5a2af73b/transformations-2026.1.18-cp314-cp314-win_arm64.whl", hash = "sha256:ceca0b448e55113827ec145bb6dedf8b6968a25151af58d8651524e860ab0a95", size = 53526 }, + { url = "https://files.pythonhosted.org/packages/bb/b9/53fca78dc33172b839e088bd240b35856e00d97292e4ef6f2d86e2310300/transformations-2026.1.18-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:8ef426d4939a4fd3d31671239f81a0bea4be65132f44985cad85570ec8ba7a94", size = 59541 }, + { url = "https://files.pythonhosted.org/packages/bd/e9/b9922e80d7dacfe859b7dccbaff7bfe73176b2378470eea43f2078be2c0d/transformations-2026.1.18-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:8a213c6b7399b3bcf794124b20e7b2ec45cd73d39bad47a30f840e62ed26577d", size = 55910 }, + { url = "https://files.pythonhosted.org/packages/81/3c/9472122b19e2fdc8ab5b2ba5301b2342ade8bac48995ce3915a0454e1ad7/transformations-2026.1.18-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:10ad97e2941e75311fb97515926f5764701a0687e0f587c0aaa88c8d7f0e1fd0", size = 165424 }, + { url = "https://files.pythonhosted.org/packages/a4/44/30d8c122e30cce19e6a089a4519303a7761226be4222e5f3df2aa7d0b27a/transformations-2026.1.18-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9188136a4a93690b857d6d9b37f87f61cf87f311c6cf1e642e326d09a9791c2a", size = 169540 }, + { url = "https://files.pythonhosted.org/packages/7b/38/ad5fa7c5fb52d9ae14887fa85e230535de6453abc2ec64e8e4405edf9e35/transformations-2026.1.18-cp314-cp314t-win32.whl", hash = "sha256:b4ac6a478752a88eceacdc885eae927fbfd4fc60fd903450a93a9894d51768f1", size = 58067 }, + { url = "https://files.pythonhosted.org/packages/f3/1b/81b9da43d58b3239fcaf5b48059e3548cc7fc079ab1f1b7877989d4f3bda/transformations-2026.1.18-cp314-cp314t-win_amd64.whl", hash = "sha256:ffbf7bc8cd2a3a95414ebe34add218e61fd859b7bae966ea04987ce48d41b180", size = 64072 }, + { url = "https://files.pythonhosted.org/packages/33/19/95496c9e1d053f6c8eb4e8c759a9603abf54bcf6fd775cb1e1c0c48e6040/transformations-2026.1.18-cp314-cp314t-win_arm64.whl", hash = "sha256:3983f220574d0cc84c87fb4f22c8df1176bdfedde4e92a79d91747705c27d098", size = 54421 }, +] + +[[package]] +name = "typing-extensions" +version = "4.16.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f6/cc/6253133b5bb138fc3306cebfbda2c520f545d36b5be2c7255cc528bb45d6/typing_extensions-4.16.0.tar.gz", hash = "sha256:dc983d19a509c94dba722ee6abd33940f7c05a89e243c47e907eb4db6f1a43e5", size = 113555 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/49/d3/b8441a820a491ddfc024b0b0cf0393375b75ea13866d9c66727e54c2fc80/typing_extensions-4.16.0-py3-none-any.whl", hash = "sha256:481caa481374e813c1b176ada14e97f1f67a4539ce9cfeb3f350d78d6370c2e8", size = 45571 }, +] + +[[package]] +name = "ubelt" +version = "1.4.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/97/39/a2ca653ee2500ae963e38b6b3a38d02f7c4ea72a3e5af410c6d66f058ed6/ubelt-1.4.2.tar.gz", hash = "sha256:40322cf3bb59c05b7567222b10bd0d553c6abc463cd480c23ea3dbcf2ef332fb", size = 305863 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/01/b9/b6ee2a03975b398b428b1ed8ce2f1a598e2b9cc01ad2c6d49cfce3bb5a9b/ubelt-1.4.2-py3-none-any.whl", hash = "sha256:6cdbcbba75cd7c3e1ef9556a13b7c0e29a55f67cb454f022a981e42808f2b06e", size = 231820 }, +] + +[[package]] +name = "wcwidth" +version = "0.8.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/34/74/c6428f875774288bec1396f5bfcbc2d925700a4dad61727fd5f2b12f249d/wcwidth-0.8.2.tar.gz", hash = "sha256:91fbef97204b96a3d4d421609b80340b760cf33e26da123ff243d76b1fda8dda", size = 1466253 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/96/42/3e5985a0a7e57de470b320c6d6a1a67c844f6737a587f3d44dd13d1819e7/wcwidth-0.8.2-py3-none-any.whl", hash = "sha256:d63947694a0539a1d51e01eda7caf800c291020e6cdd7e28ad7b14dd33ad4f85", size = 323166 }, +] + +[[package]] +name = "zope-interface" +version = "8.5" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/08/dc/50550cfcbb2ea3cbca5f1d7ed05c8aa840f831a0f2d63aec0a953f7c590e/zope_interface-8.5.tar.gz", hash = "sha256:7a3ba1c5877f0f3e3906b02ddf793abed2becc2948116414ce0e1dd820b68d6d", size = 257957 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b6/43/9cd98bee951d23848de690ba2809f87e3b22c67c370987acc960da15ad37/zope_interface-8.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c0c8aa2bf8f3911ef37b87deb1bbe225a310e6eb6522a16d77f5d8330c4f6fbe", size = 210951 }, + { url = "https://files.pythonhosted.org/packages/17/0f/8f1a29966bcf863e3a2121edcafb81c55715de7886bcc9544749cc79e7da/zope_interface-8.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:efe234a0fafb4b6b1602e9be9245b97c2bf06d67c07af5a4bc3c0438978b555c", size = 211309 }, + { url = "https://files.pythonhosted.org/packages/9f/9f/37e564eaaf85e3abc1ada40a79fa43f2ab45bdb67431b0ec0fe29e4763e2/zope_interface-8.5-cp310-cp310-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:dabeb6fe1228d411994f300811edc6866fff0cdcbc9cef98a78f05ea0da42e37", size = 254881 }, + { url = "https://files.pythonhosted.org/packages/06/61/e6501d8ea7a2cac3217e03f404e1f98c1df7191d83cfe86b1895fbba5dac/zope_interface-8.5-cp310-cp310-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:147a9442dcc2b7339ecdb1be2b3cdb098e90462e39425054053ebfb50d99125a", size = 259811 }, + { url = "https://files.pythonhosted.org/packages/91/15/bfa25ef480b02af6e9452c478483fec75e87c9e2b60c407fd0b1f6054b9c/zope_interface-8.5-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:a17e681224267880707c9ec9e730ad9a1ad2d65c371256843efba6cf48711b58", size = 260358 }, + { url = "https://files.pythonhosted.org/packages/64/51/2b518072fea76242da64451d501c69b7b5ccdef9b57fead584ccf1c180d5/zope_interface-8.5-cp310-cp310-win_amd64.whl", hash = "sha256:d178968a1a611df30549a717d1624cb38ca810347339e3e37b7baa6f6781a170", size = 214822 }, + { url = "https://files.pythonhosted.org/packages/ce/f1/83ad110fb847413affe71609bb50e59e1aa082e1236030122227c7c283d3/zope_interface-8.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:afc66ccaef2a3c0bef6ca02aad40d29a39276389dad16a8eac36f9f385e4d057", size = 211426 }, + { url = "https://files.pythonhosted.org/packages/bb/a7/6b6e0c31ac240cb9fc015ae9ed45ca54be886c18fcf7bfa2377a4d7a8785/zope_interface-8.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c28044972187245d7a309e4699319bfdbd2ffcbf7176d1d4ddf5adffb2dea80f", size = 211850 }, + { url = "https://files.pythonhosted.org/packages/37/36/7599ecabcf80ce4fef2e1ef3c5ac0d4696b61f03f724cc44022f4d226af9/zope_interface-8.5-cp311-cp311-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:03bbecc7982af713d7499d4084bc03916413d17ffd45f89009348cc0c1d9e376", size = 260711 }, + { url = "https://files.pythonhosted.org/packages/03/3e/1774b0ee46ccbb5498ee3c33ece40315b6ef58bc71957be94bd345340bc1/zope_interface-8.5-cp311-cp311-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:bf917009a4a7457c7290225a019f4a0aa706d96accd2cfdba2418d3bc1fcde2f", size = 265277 }, + { url = "https://files.pythonhosted.org/packages/b6/09/e533b2ffabaae4e5d5730d6768a591cf335defe8e37bec2ad905d09be656/zope_interface-8.5-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:31cff25b2aaedb5267e6e77b1e9be6b0ec4f622032de8a069202b8ffacda7dc2", size = 266369 }, + { url = "https://files.pythonhosted.org/packages/49/4a/3ebe6a4c122b2d5340db45cbe7e490663d3228b172710ec71060cd5d541e/zope_interface-8.5-cp311-cp311-win_amd64.whl", hash = "sha256:17a3114bbdddb5e75e5784cdf318944636190cbbc72d357ef9fb1a8b0351f955", size = 215161 }, + { url = "https://files.pythonhosted.org/packages/d2/59/056ad97af5b16db1975ee98ec7ab03d2ce3f3355efad904ced1dbce0e39f/zope_interface-8.5-cp311-cp311-win_arm64.whl", hash = "sha256:aab6bb5bee10f38ea688b95ba054396b67f613552d2c8378be7fcb2d2fba7646", size = 213481 }, + { url = "https://files.pythonhosted.org/packages/97/cc/b84123a948f3162a34623e188922827cd845244fdd043ed20f8d02228caa/zope_interface-8.5-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:8e6ee90c2e6de7c37058d5fa41f123c8b13a312db8d1e0fb5840d7f4bcdff9c9", size = 212165 }, + { url = "https://files.pythonhosted.org/packages/4e/78/cbceec44f1b27208a76c1a688c131302685852406a23df5aab68324109cc/zope_interface-8.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c1adc90d3576b3b4c4de4953e6002c37bef28b78d7fa54c1bbfd0c50f022fe7c", size = 212341 }, + { url = "https://files.pythonhosted.org/packages/e1/c3/005032195ff3b210c139b7c560ed5c534e844b0907d8e44d2b3d8919305e/zope_interface-8.5-cp312-cp312-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:e6347b8d8d12c5eca6502450a92be30079b7acfade2c4f693efa0deb8871b06e", size = 265296 }, + { url = "https://files.pythonhosted.org/packages/c5/66/1036543d6a66bc04c19df3cf650f3ad938a002ab0a443c24e23e8de5e8b9/zope_interface-8.5-cp312-cp312-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:5e970dabea777a24b0b0bbf9dae3ab75ce8b2d8e948edf4875627034b21f3560", size = 270689 }, + { url = "https://files.pythonhosted.org/packages/30/4c/8b56259558cace4414e753ca6740396a1f59d4a95ddb55b4658600408670/zope_interface-8.5-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f0b48ccadaa9839e09ff81e969703cecb3f402c813bfe8b958652e699bea69f5", size = 270280 }, + { url = "https://files.pythonhosted.org/packages/f9/ea/649908c83aa8fdb7faf2ddca4d3cf6fb8f2157121267dc56e8f72681e26c/zope_interface-8.5-cp312-cp312-win_amd64.whl", hash = "sha256:e0e311f1277468c08fd59a2b41f71b43d25dff639789d364747acd1705c0df6e", size = 215019 }, + { url = "https://files.pythonhosted.org/packages/9f/97/da13037b4c563e4df32eedbc819f8c00b754af494f68211e3dffd48d52da/zope_interface-8.5-cp312-cp312-win_arm64.whl", hash = "sha256:652b73107a04159ec6c020db6c1543d4f1e8f4d069bd2aac88a947820923517b", size = 213569 }, + { url = "https://files.pythonhosted.org/packages/f4/8c/4c15755d701f2ec0e80d64a18e1ebaf5be2c584c0ec153fd516f5d13eada/zope_interface-8.5-cp313-cp313-macosx_10_9_x86_64.whl", hash = "sha256:28e80457c134d1fa57a7d758004dece348654e1b1467ac22dcdc20fc1d127c52", size = 212512 }, + { url = "https://files.pythonhosted.org/packages/9a/2e/4360c54c465db042cc8fbeeec92abac28b4cedbf6ba63c1f092fd08a190f/zope_interface-8.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:09495ce9d559c06b70f2d4855b3e4f48a822a9ddc8be1d30c5b4e5be14ae1ace", size = 212541 }, + { url = "https://files.pythonhosted.org/packages/aa/a5/692a2b8d70f78e848793231d5fae5fecbf8d0cccd73430fdc34802a6d3c1/zope_interface-8.5-cp313-cp313-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:7849ad8fa90763cc1087f4dda78ca3a233e950b3e08fac7079297c9cafbbd7bb", size = 265191 }, + { url = "https://files.pythonhosted.org/packages/70/8d/454a9cfc7a050c394ab4f11b3371f7897828b7415e096afff724637e65e0/zope_interface-8.5-cp313-cp313-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:5578c9421ca409a1f39f153d6f7803e4cde01da592ec75a9ac5e1b777d18d33b", size = 270626 }, + { url = "https://files.pythonhosted.org/packages/51/8c/db8409cfa3575b8e9b4800babd7d49f8228433cd1f0c56814bd0ada49c33/zope_interface-8.5-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:e1bd7d96b4ca5fa311f54c9eac16dce4886b428c1531dbe06067763ccdf123b4", size = 270444 }, + { url = "https://files.pythonhosted.org/packages/4a/df/a386940e41469ef615e100a216d8b386521e9e598817147f87932ca203c4/zope_interface-8.5-cp313-cp313-win_amd64.whl", hash = "sha256:0c8123d2a4dfde2a613c7cb772605477724782c20bc2e0ad1d9435376a6a44a3", size = 215021 }, + { url = "https://files.pythonhosted.org/packages/89/75/477eb5669b6b2a7a843decd1a075e9b1971a8720017654143a7183abd3d9/zope_interface-8.5-cp313-cp313-win_arm64.whl", hash = "sha256:6d02be14f3173c6c7288bc2fdf530090c01c3cf8764ad46c68024686f364278e", size = 213610 }, + { url = "https://files.pythonhosted.org/packages/d4/19/5032e954827fdf02db2d2f49737ac4378bb9cfc2cd95a8f2e2a5ae2ec01a/zope_interface-8.5-cp314-cp314-macosx_10_9_x86_64.whl", hash = "sha256:ffaecf013251a89d0de6feb49a46eba48ad8cbbf8a40aeb6045e459e7bec6784", size = 212597 }, + { url = "https://files.pythonhosted.org/packages/f1/53/3ef644012cf8a6a234a2d6134aab5a5c65ac5467c86296865501d4fbc406/zope_interface-8.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:126fa9d1c52295ae076d4cf968634f0a1826afa408a20808b57ff72877b8f69f", size = 212626 }, + { url = "https://files.pythonhosted.org/packages/32/67/bc8b4f465d388039255003e230c284a175cedf1203c692f23cb7bff64efe/zope_interface-8.5-cp314-cp314-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:3090e3a663d20194756a59a272e0c8508b889341e31d5894223331fe6b4f9b21", size = 266827 }, + { url = "https://files.pythonhosted.org/packages/a7/eb/37d05b935ede53d79690fecc8d201440084418e590bcfc05f384451c7593/zope_interface-8.5-cp314-cp314-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:9342fb74e2afefdb081bf1df727d209ea56995c6e13f5a0540e6d7aff4beafb8", size = 270139 }, + { url = "https://files.pythonhosted.org/packages/8b/0b/fd0c54579e2ce8dc6cf1a757903f3374bc6fbda929a46af9e0f53cb0e5f0/zope_interface-8.5-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:6c54725d818f1b57a7efb8b16528326e1f3c257b602b32393fd255c45af8799d", size = 270338 }, + { url = "https://files.pythonhosted.org/packages/c1/1d/c420dcd777bb761067ea92879ac766694a5ca78608185f1aecea64cbfc11/zope_interface-8.5-cp314-cp314-win_amd64.whl", hash = "sha256:29d74febbae1afeb6834c4ccbf42e242a673c860060f09e53142825270456140", size = 215789 }, + { url = "https://files.pythonhosted.org/packages/62/94/50b5eb8f94e527edceac14f9955e58917424ea79bb572ddc18548561cbc2/zope_interface-8.5-cp314-cp314-win_arm64.whl", hash = "sha256:633c8c49396f38df030340797c533e9fe460d1b5d1e42d88e55e938e525f548c", size = 213757 }, + { url = "https://files.pythonhosted.org/packages/17/6f/5d5f32c4dfcdb16ce2ec5363da686840f13c13e1a1214cb70b49e1cd6d9f/zope_interface-8.5-cp314-cp314t-macosx_10_9_x86_64.whl", hash = "sha256:133999820fdbae513c36c03d6f29ef87317aaa3edef39112222b155083664714", size = 213591 }, + { url = "https://files.pythonhosted.org/packages/f3/55/de0c3459ff717fce3342f9a29464c281fdeb0d36c3171ee88d119d5f0650/zope_interface-8.5-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:8bd75c96966e573232f0599deaff717564828031c7f05563ccc1ac35c5ee0304", size = 213733 }, + { url = "https://files.pythonhosted.org/packages/c2/95/d97430abd5ae9677e8b9295b58720c0064a5b557dbb6b8bf5928484cf0d8/zope_interface-8.5-cp314-cp314t-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:14b0e9799351d4c34fe99afd67f0cdd76e55ba15c66a98699d5fc22ea8241e08", size = 294905 }, + { url = "https://files.pythonhosted.org/packages/41/ec/a0f8f3dad6e74992f4654bdd94802be0929eabca7b871cac3b6fbb5e961b/zope_interface-8.5-cp314-cp314t-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:0cd6a732ac84b94eb1ef9222a117347a27efd294ee16810ffdf7ecd307677ed5", size = 300885 }, + { url = "https://files.pythonhosted.org/packages/0f/da/6881b48803a0ee8d23eb5efa30fce3ed218a2bd9de5758ce489d224fee81/zope_interface-8.5-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:798b7c87d0e59a7d5d086d642208d0d8700ff0d55c4029134b3c479c3bfb110f", size = 304672 }, + { url = "https://files.pythonhosted.org/packages/2e/0e/b4c01320859ff1d585438bc231fd60bd258d096359bccf6654fecdf0cffb/zope_interface-8.5-cp314-cp314t-win_amd64.whl", hash = "sha256:0fc3a9d45f114d27eaa1e53beeb144533689edca8a9f66505b1e8e8b3f075e42", size = 217241 }, +]