From ba6c91bda054cee5ce8904600847e8d63df9855a Mon Sep 17 00:00:00 2001 From: ramyamounir <44840015+ramyamounir@users.noreply.github.com> Date: Fri, 28 Nov 2025 09:39:03 -0500 Subject: [PATCH 01/15] feat: interactive plot for pointcloud hypothesis space fix: pretrained models class descriptions and shim unpickler fix: remove print message in widgets.py feat: extract_slider_state round parameter feat: add extract button state helper function feat: add interactive pointcloud plot --- pyproject.toml | 1 + src/tbp/interactive/data.py | 83 +- src/tbp/interactive/widgets.py | 20 +- ...interactive_hypothesis_space_pointcloud.py | 1570 +++++++++++++++++ uv.lock | 612 ++++++- 5 files changed, 2282 insertions(+), 4 deletions(-) create mode 100644 src/tbp/plot/plots/interactive_hypothesis_space_pointcloud.py diff --git a/pyproject.toml b/pyproject.toml index 62767e1..fd48d1d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -21,6 +21,7 @@ dependencies = [ "vedo", "pypubsub", "scipy>=1.16.2", + "torch-geometric>=2.7.0", ] description = "A visualization tool for plotting tbp.monty visualizations." dynamic = ["version"] diff --git a/src/tbp/interactive/data.py b/src/tbp/interactive/data.py index e781ace..daeb4a9 100644 --- a/src/tbp/interactive/data.py +++ b/src/tbp/interactive/data.py @@ -9,14 +9,17 @@ from __future__ import annotations import os +import pickle +import types from copy import deepcopy from dataclasses import dataclass from pathlib import Path from typing import TYPE_CHECKING, Any, Literal import numpy as np +import torch import trimesh -from vedo import Mesh +from vedo import Mesh, Points from tbp.plot.plots.stats import deserialize_json_chunks @@ -100,6 +103,84 @@ def create_mesh(self, obj_name: str) -> Mesh: return obj +class _MontyShimUnpickler(pickle.Unpickler): + """Unpickler that shims out ``tbp.monty`` classes with lightweight dummies. + + Any class reference from a module path starting with ``tbp.monty`` is + redirected to a dynamically created dummy class. This allows deserialization + of checkpoints that reference ``tbp.monty`` types even when the actual + package is not installed, while still making object attributes + (e.g., ``.pos``, ``.x``) accessible. + + Dummy classes are cached by ``(module, name)`` so that repeated lookups + return the same type. + """ + + _cache = {} # cache dummy classes + + def find_class(self, module, name): + if module.startswith("tbp.monty"): + key = (module, name) + cls = self._cache.get(key) + if cls is None: + cls = type(name, (), {}) + self._cache[key] = cls + return cls + return super().find_class(module, name) + + +class PretrainedModelsLoader: + """Load Monty pretrained Models as point cloud Vedo object.""" + + def __init__(self, data_path: str, lm_id: int = 0, input_channel: str = "patch"): + """Initialize the loader. + + Args: + data_path: Path to the `model.pt` file holding the pretrained models. + lm_id: Which learning module to use when extracting the pretrained graphs. + input_channel: Which channel to use for extracting the pretrained graphs. + """ + self.path = data_path + models = self._torch_load_with_optional_shim()["lm_dict"][lm_id]["graph_memory"] + self.graphs = {k: v[input_channel]._graph for k, v in models.items()} + + def _torch_load_with_optional_shim(self, map_location="cpu"): + """Load a torch checkpoint with optional fallback for tbp.monty shimming. + + Try a standard torch.load first (weights_only=False because we need objects). + If tbp.monty isn't installed and the checkpoint references it, optionally + retry using a restricted Unpickler that only dummies tbp.monty.* symbols. + + Args: + map_location: Device mapping passed to `torch.load` (default: "cpu"). + + Returns: + The deserialized checkpoint object. + + Raises: + ModuleNotFoundError: If a missing module other than `tbp.monty` is required. + """ + try: + return torch.load(self.path, map_location=map_location, weights_only=False) + except ModuleNotFoundError as e: + # Only intercept the specific missing tbp.monty namespace + if "tbp.monty" not in str(e): + raise + + shim_pickle_module = types.ModuleType("monty_shim_pickle") + shim_pickle_module.Unpickler = _MontyShimUnpickler + + return torch.load( + self.path, + map_location=map_location, + weights_only=False, + pickle_module=shim_pickle_module, + ) + + def create_model(self, obj_name: str) -> Points: + return Points(self.graphs[obj_name].pos.numpy(), r=4, c="gray") + + class DataParser: """Parser that navigates nested JSON-like data using a `DataLocator`. diff --git a/src/tbp/interactive/widgets.py b/src/tbp/interactive/widgets.py index f341d99..ad0737e 100644 --- a/src/tbp/interactive/widgets.py +++ b/src/tbp/interactive/widgets.py @@ -27,16 +27,32 @@ ) -def extract_slider_state(widget: Slider2D) -> int: +def extract_button_state(widget: Button) -> str: + """Read the Button state. + + Args: + widget: The Vedo Button. + + Returns: + The current button state as a string. + """ + return widget.status() + + +def extract_slider_state(widget: Slider2D, round_value: bool = True) -> int: """Read the slider state and round it to an integer value. Args: widget: The Vedo slider. + round_value: Whether to round the value to an integer or keep it as float. Returns: The current slider value rounded to the nearest integer. """ - return round(widget.GetRepresentation().GetValue()) + value = widget.GetRepresentation().GetValue() + if round_value: + value = round(value) + return value def set_slider_state(widget: Slider2D, value: Any) -> None: diff --git a/src/tbp/plot/plots/interactive_hypothesis_space_pointcloud.py b/src/tbp/plot/plots/interactive_hypothesis_space_pointcloud.py new file mode 100644 index 0000000..c52054e --- /dev/null +++ b/src/tbp/plot/plots/interactive_hypothesis_space_pointcloud.py @@ -0,0 +1,1570 @@ +# Copyright 2025 Thousand Brains Project +# +# Copyright may exist in Contributors' modifications +# and/or contributions to the work. +# +# Use of this source code is governed by the MIT +# license that can be found in the LICENSE file or at +# https://opensource.org/licenses/MIT. + +from __future__ import annotations + +import bisect +import logging +from copy import deepcopy +from pathlib import Path +from typing import TYPE_CHECKING, Any + +import matplotlib.pyplot as plt +import numpy as np +import pandas as pd +from pubsub.core import Publisher +from vedo import Button, Image, Line, Mesh, Plotter, Points, Slider2D, Sphere + +from tbp.interactive.data import ( + DataLocator, + DataLocatorStep, + DataParser, + PretrainedModelsLoader, + YCBMeshLoader, +) +from tbp.interactive.topics import TopicMessage, TopicSpec +from tbp.interactive.utils import ( + Location3D, +) +from tbp.interactive.widget_updaters import WidgetUpdater +from tbp.interactive.widgets import ( + VtkDebounceScheduler, + Widget, + extract_button_state, + extract_slider_state, + set_button_state, + set_slider_state, +) +from tbp.plot.registry import attach_args, register + +if TYPE_CHECKING: + import argparse + from collections.abc import Callable, Iterable + +logger = logging.getLogger(__name__) + + +class StepMapper: + """Bidirectional mapping between global step indices and (episode, local_step). + + Global steps are defined as the concatenation of all local episode steps: + + episode 0: steps [0, ..., n0 - 1] + episode 1: steps [0, ..., n1 - 1] + ... + + Global index is: + [0, ..., n0 - 1, n0, ..., n0 + n1 - 1, ...] + """ + + def __init__(self, data_parser: DataParser) -> None: + self.data_parser = data_parser + self._locators = self._create_locators() + + # number of steps in each episode + self._episode_lengths: list[int] = self._compute_episode_lengths() + + # global offset of each episode + self._prefix_sums: list[int] = self._compute_prefix_sums() + + def _create_locators(self) -> dict[str, DataLocator]: + """Create and return data locators used to access episode steps. + + Returns: + A dictionary containing the created locators. + """ + locators = {} + locators["step"] = DataLocator( + path=[ + DataLocatorStep.key(name="episode"), + DataLocatorStep.key(name="lm", value="LM_0"), + DataLocatorStep.key( + name="telemetry", value="hypotheses_updater_telemetry" + ), + ] + ) + return locators + + def _compute_episode_lengths(self) -> list[int]: + locator = self._locators["step"] + + episode_lengths: list[int] = [] + + for episode in self.data_parser.query(locator): + episode_lengths.append( + len(self.data_parser.extract(locator, episode=episode)) + ) + + if not episode_lengths: + raise RuntimeError("No episodes found while computing episode lengths.") + + return episode_lengths + + def _compute_prefix_sums(self) -> list[int]: + prefix_sums = [0] + for length in self._episode_lengths: + prefix_sums.append(prefix_sums[-1] + length) + return prefix_sums + + @property + def num_episodes(self) -> int: + return len(self._episode_lengths) + + @property + def total_num_steps(self) -> int: + """Total number of steps across all episodes.""" + return self._prefix_sums[-1] + + def global_to_local(self, global_step: int) -> tuple[int, int]: + """Convert a global step index into (episode, local_step). + + Args: + global_step: Global step index in the range + `[0, total_num_steps)`. + + Returns: + A pair `(episode, local_step)` such that: + * `episode` is the zero based episode index. + * `local_step` is the zero based step index within that episode. + + Raises: + IndexError: If `global_step` is negative or not less than + `total_num_steps`. + """ + if global_step < 0 or global_step >= self.total_num_steps: + raise IndexError( + f"global_step {global_step} is out of range [0, {self.total_num_steps})" + ) + + episode = bisect.bisect_right(self._prefix_sums, global_step) - 1 + local_step = global_step - self._prefix_sums[episode] + return episode, local_step + + def local_to_global(self, episode: int, step: int) -> int: + """Convert an (episode, local_step) pair into a global step index. + + Args: + episode: Zero based episode index in the range + `[0, num_episodes)`. + step: Zero based step index within the given episode. Must be in + `[0, number_of_steps_in_episode)`. + + Returns: + The corresponding global step index, in the range + `[0, total_num_steps)`. + + Raises: + IndexError: If `episode` is out of range, or if `step` is out of + range for the given episode. + """ + if episode < 0 or episode >= self.num_episodes: + raise IndexError( + f"episode {episode} is out of range [0, {self.num_episodes})" + ) + + num_steps_in_episode = self._episode_lengths[episode] + if step < 0 or step >= num_steps_in_episode: + raise IndexError( + f"step {step} is out of range [0, {num_steps_in_episode}) " + f"for episode {episode}" + ) + + return self._prefix_sums[episode] + step + + +class StepSliderWidgetOps: + """WidgetOps implementation for a Step slider. + + This class adds a slider widget for the global step. It uses the step mapper + to retrieve information about the total number of steps and the mapping between + global and local step indices. The published state is in the local format (i.e., + episode and local step values). + + Attributes: + plotter: A `vedo.Plotter` object to add or remove the slider and render. + data_parser: A parser that extracts or queries information from the + JSON log file. + step_mapper: A mapper between local and global step indices. + updaters: A list with a single `WidgetUpdater` that reacts to the + `"episode_number"` topic and calls `update_slider_range`. + _add_kwargs: Default keyword arguments passed to `plotter.add_slider`. + _locators: Data accessors keyed by name that instruct the `DataParser` + how to retrieve the required information. + """ + + def __init__( + self, + plotter: Plotter, + data_parser: DataParser, + step_mapper: StepMapper, + ) -> None: + self.plotter = plotter + self.data_parser = data_parser + + self._add_kwargs = { + "xmin": 0, + "xmax": 10, + "value": 0, + "pos": [(0.11, 0.06), (0.89, 0.06)], + "title": "Step", + "show_value": False, + } + + self.step_mapper = step_mapper + self.current_episode = -1 + + def add(self, callback: Callable) -> Slider2D: + kwargs = deepcopy(self._add_kwargs) + kwargs.update({"xmax": self.step_mapper.total_num_steps - 1}) + widget = self.plotter.at(0).add_slider(callback, **kwargs) + self.plotter.at(0).render() + return widget + + def remove(self, widget: Slider2D) -> None: + self.plotter.at(0).remove(widget) + self.plotter.at(0).render() + + def extract_state(self, widget: Slider2D) -> int: + return extract_slider_state(widget) + + def set_state(self, widget: Slider2D, value: int) -> None: + set_slider_state(widget, value) + + def state_to_messages(self, state: int) -> Iterable[TopicMessage]: + episode, step = self.step_mapper.global_to_local(state) + + messages = [] + + # Only publish episode number if changed + if self.current_episode != episode: + messages.append(TopicMessage(name="episode_number", value=episode)) + self.current_episode = episode + + messages.append(TopicMessage(name="step_number", value=step)) + + return messages + + +class GtMeshWidgetOps: + """WidgetOps implementation for rendering the ground-truth target mesh. + + This widget listens for "episode_number" and "step_number" to update the + ground-truth primary target mesh and the agent/patch location on the object + at the current step. The widget also listens for buttons that show/hide the + agent and sensor patch history locations. + + This widget does not publish any messages. + """ + + def __init__( + self, plotter: Plotter, data_parser: DataParser, ycb_loader: YCBMeshLoader + ): + self.plotter = plotter + self.data_parser = data_parser + self.ycb_loader = ycb_loader + self.updaters = [ + WidgetUpdater( + topics=[ + TopicSpec("episode_number", required=True), + TopicSpec("alpha_value", required=True), + ], + callback=self.update_mesh, + ), + WidgetUpdater( + topics=[ + TopicSpec("episode_number", required=True), + TopicSpec("step_number", required=True), + ], + callback=self.update_sensor, + ), + WidgetUpdater( + topics=[ + TopicSpec("episode_number", required=True), + TopicSpec("step_number", required=True), + TopicSpec("sensor_path_button", required=True), + ], + callback=self.update_sensor_path, + ), + WidgetUpdater( + topics=[ + TopicSpec("episode_number", required=True), + TopicSpec("step_number", required=True), + TopicSpec("patch_path_button", required=True), + ], + callback=self.update_patch_path, + ), + WidgetUpdater( + topics=[ + TopicSpec("alpha_value", required=True), + ], + callback=self.update_alpha, + ), + ] + self._locators = self.create_locators() + + self.sensor_sphere: Sphere | None = None + self.gaze_line: Line | None = None + + self.sensor_path_spheres: list[Sphere] = [] + self.sensor_path_line: Line | None = None + + self.patch_path_spheres: list[Sphere] = [] + self.patch_path_line: Line | None = None + + def create_locators(self) -> dict[str, DataLocator]: + locators = {} + locators["target"] = DataLocator( + path=[ + DataLocatorStep.key(name="episode"), + DataLocatorStep.key(name="lm", value="target"), + ] + ) + + locators["steps_mask"] = DataLocator( + path=[ + DataLocatorStep.key(name="episode"), + DataLocatorStep.key(name="system", value="LM_0"), + DataLocatorStep.key(name="telemetry", value="lm_processed_steps"), + ] + ) + + locators["sensor_location"] = DataLocator( + path=[ + DataLocatorStep.key(name="episode"), + DataLocatorStep.key(name="system", value="motor_system"), + DataLocatorStep.key(name="telemetry", value="action_sequence"), + DataLocatorStep.index(name="sm_step"), + DataLocatorStep.index(name="telemetry_type", value=1), + DataLocatorStep.key(name="agent", value="agent_id_0"), + DataLocatorStep.key(name="pose", value="position"), + ] + ) + + locators["patch_location"] = DataLocator( + path=[ + DataLocatorStep.key(name="episode"), + DataLocatorStep.key(name="system", value="LM_0"), + DataLocatorStep.key(name="telemetry", value="locations"), + DataLocatorStep.key(name="sm", value="patch"), + DataLocatorStep.index(name="step"), + ] + ) + + return locators + + def remove(self, widget: Mesh) -> None: + if widget is not None: + self.plotter.at(1).remove(widget) + self.plotter.at(1).render() + + def update_mesh( + self, widget: Mesh | None, msgs: list[TopicMessage] + ) -> tuple[Mesh | None, bool]: + """Update the target mesh when the episode changes. + + Removes any existing mesh, loads the episode's primary target object, + applies its Euler rotations, scales and positions it, then adds it to + the plotter. + + Args: + widget: The currently displayed mesh, if any. + msgs: Messages received from the `WidgetUpdater`. + + Returns: + A tuple `(mesh, False)`. The second value is `False` to indicate + that no publish should occur. + """ + self.remove(widget) + msgs_dict = {msg.name: msg.value for msg in msgs} + + locator = self._locators["target"] + target = self.data_parser.extract( + locator, episode=str(msgs_dict["episode_number"]) + ) + target_id = target["primary_target_object"] + target_rot = target["primary_target_rotation_euler"] + target_pos = target["primary_target_position"] + widget = self.ycb_loader.create_mesh(target_id).clone(deep=True) + widget.rotate_x(target_rot[0]) + widget.rotate_y(target_rot[1]) + widget.rotate_z(target_rot[2]) + widget.shift(*target_pos) + widget.alpha(msgs_dict["alpha_value"]) + + self.plotter.at(1).add(widget) + self.plotter.at(1).render() + + return widget, False + + def update_sensor( + self, widget: Mesh | None, msgs: list[TopicMessage] + ) -> tuple[Mesh | None, bool]: + """Update the agent and sensor patch location on the object. + + Args: + widget: The currently displayed mesh, if any. + msgs: Messages received from the `WidgetUpdater`. + + Returns: + A tuple `(widget, False)`. The second value is `False` to indicate + that no publish should occur. + """ + msgs_dict = {msg.name: msg.value for msg in msgs} + episode_number = msgs_dict["episode_number"] + step_number = msgs_dict["step_number"] + + steps_mask = self.data_parser.extract( + self._locators["steps_mask"], episode=str(episode_number) + ) + mapping = np.flatnonzero(steps_mask) + + sensor_pos = self.data_parser.extract( + self._locators["sensor_location"], + episode=str(episode_number), + sm_step=int(mapping[step_number]), + ) + + patch_pos = self.data_parser.extract( + self._locators["patch_location"], + episode=str(episode_number), + step=step_number, + ) + + if self.sensor_sphere is None: + self.sensor_sphere = Sphere(pos=sensor_pos, r=0.004) + self.plotter.at(1).add(self.sensor_sphere) + self.sensor_sphere.pos(sensor_pos) + + if self.gaze_line is None: + self.gaze_line = Line(sensor_pos, patch_pos, c="black", lw=4) + self.plotter.at(1).add(self.gaze_line) + self.gaze_line.points = [sensor_pos, patch_pos] + + self.plotter.at(1).render() + + return widget, False + + def _clear_sensor_path(self) -> None: + for s in self.sensor_path_spheres: + self.plotter.at(1).remove(s) + self.sensor_path_spheres.clear() + if self.sensor_path_line is not None: + self.plotter.at(1).remove(self.sensor_path_line) + self.sensor_path_line = None + + def update_sensor_path( + self, widget: Mesh | None, msgs: list[TopicMessage] + ) -> tuple[Mesh | None, bool]: + msgs_dict = {msg.name: msg.value for msg in msgs} + episode_number = msgs_dict["episode_number"] + step_number = msgs_dict["step_number"] + path_button = msgs_dict["sensor_path_button"] + + # Clear existing path + self._clear_sensor_path() + + if path_button == "Sensor Path": + steps_mask = self.data_parser.extract( + self._locators["steps_mask"], episode=str(episode_number) + ) + mapping = np.flatnonzero(steps_mask) + + if len(mapping) == 0: + self.plotter.at(1).render() + return widget, False + + # Clamp step_number to valid range + max_step_idx = min(step_number, len(mapping) - 1) + + # Collect all sensor positions up to the current step + points: list[np.ndarray] = [] + for k in range(max_step_idx + 1): + sensor_pos = self.data_parser.extract( + self._locators["sensor_location"], + episode=str(episode_number), + sm_step=int(mapping[k]), + ) + points.append(sensor_pos) + + # Create small spheres at each position + for p in points: + sphere = Sphere(pos=p, r=0.002) + self.plotter.at(1).add(sphere) + self.sensor_path_spheres.append(sphere) + + # Create a polyline connecting all points + if len(points) >= 2: + self.sensor_path_line = Line(points, c="red", lw=1) + self.plotter.at(1).add(self.sensor_path_line) + + self.plotter.at(1).render() + return widget, False + + def _clear_patch_path(self) -> None: + for s in self.patch_path_spheres: + self.plotter.at(1).remove(s) + self.patch_path_spheres.clear() + if self.patch_path_line is not None: + self.plotter.at(1).remove(self.patch_path_line) + self.patch_path_line = None + + def update_patch_path( + self, widget: None, msgs: list[TopicMessage] + ) -> tuple[None, bool]: + msgs_dict = {msg.name: msg.value for msg in msgs} + episode_number = msgs_dict["episode_number"] + step_number = msgs_dict["step_number"] + path_button = msgs_dict["patch_path_button"] + + # Clear existing path + self._clear_patch_path() + + if path_button == "Patch Path": + # Collect all patch positions up to the current step + points: list[np.ndarray] = [] + max_step_idx = max(step_number, 0) + + for k in range(max_step_idx + 1): + patch_pos = self.data_parser.extract( + self._locators["patch_location"], + episode=str(episode_number), + step=k, + ) + points.append(patch_pos) + + # Create small black spheres at each patch position + for p in points: + sphere = Sphere(pos=p, r=0.002, c="black") + self.plotter.at(1).add(sphere) + self.patch_path_spheres.append(sphere) + + # Create a thin black polyline connecting all patch positions + if len(points) >= 2: + self.patch_path_line = Line(points, c="black", lw=1) + self.plotter.at(1).add(self.patch_path_line) + + self.plotter.at(1).render() + return widget, False + + def update_alpha(self, widget: None, msgs: list[TopicMessage]) -> tuple[None, bool]: + msgs_dict = {msg.name: msg.value for msg in msgs} + widget.alpha(msgs_dict["alpha_value"]) + self.plotter.at(1).render() + return widget, False + + +class AlphaSliderWidgetOps: + """WidgetOps implementation for the alpha transparency slider. + + This widget provides a slider to control the transparency of the mesh + object. It publishes on the topic `alpha_value` a float value between 0.0 and 1.0. + """ + + def __init__(self, plotter: Plotter) -> None: + self.plotter = plotter + + self._add_kwargs = { + "xmin": 0.0, + "xmax": 1.0, + "value": 1.0, + "pos": [(0.05, 0.0), (0.05, 0.4)], + "title": "Alpha", + "title_size": 2, + "slider_width": 0.04, + "tube_width": 0.015, + } + + def add(self, callback: Callable) -> Slider2D: + widget = self.plotter.at(1).add_slider(callback, **self._add_kwargs) + widget.GetRepresentation().SetLabelHeight(0.05) + self.plotter.at(1).render() + return widget + + def extract_state(self, widget: Slider2D) -> float: + return extract_slider_state(widget, round_value=False) + + def set_state(self, widget: Slider2D, value: float) -> None: + set_slider_state(widget, value) + + def state_to_messages(self, state: float) -> Iterable[TopicMessage]: + return [TopicMessage(name="alpha_value", value=state)] + + +class SensorPathButtonWidgetOps: + """WidgetOps implementation for showing/hiding the sensor path. + + This widget provides a button to switch between showing and hiding the + sensor path. The published state here is `Sensor Path` or `No Sensor Path` + and it is published on the topic `sensor_path_button`. + """ + + def __init__(self, plotter: Plotter): + self.plotter = plotter + + self._add_kwargs = { + "pos": (0.15, 0.98), + "states": ["Sensor Path", "No Sensor Path"], + "c": ["w", "w"], + "bc": ["dg", "dr"], + "size": 30, + "font": "Calco", + "bold": True, + } + + def add(self, callback: Callable) -> Button: + widget = self.plotter.at(0).add_button(callback, **self._add_kwargs) + self.plotter.at(0).render() + return widget + + def extract_state(self, widget: Button) -> str: + return extract_button_state(widget) + + def set_state(self, widget: Button, value: str) -> None: + set_button_state(widget, value) + + def state_to_messages(self, state: str) -> Iterable[TopicMessage]: + messages = [ + TopicMessage(name="sensor_path_button", value=state), + ] + return messages + + +class PatchPathButtonWidgetOps: + """WidgetOps implementation for showing/hiding the sensor patch path. + + This widget provides a button to switch between showing and hiding the + patch path. The published state here is `Patch Path` or `No Patch Path` + and it is published on the topic `patch_path_button`. + """ + + def __init__(self, plotter: Plotter): + self.plotter = plotter + + self._add_kwargs = { + "pos": (0.35, 0.98), + "states": ["Patch Path", "No Patch Path"], + "c": ["w", "w"], + "bc": ["dg", "dr"], + "size": 30, + "font": "Calco", + "bold": True, + } + + def add(self, callback: Callable) -> Button: + widget = self.plotter.at(0).add_button(callback, **self._add_kwargs) + self.plotter.at(0).render() + return widget + + def extract_state(self, widget: Button) -> str: + return extract_button_state(widget) + + def set_state(self, widget: Button, value: str) -> None: + set_button_state(widget, value) + + def state_to_messages(self, state: str) -> Iterable[TopicMessage]: + messages = [ + TopicMessage(name="patch_path_button", value=state), + ] + return messages + + +class HypSpaceWidgetOps: + """WidgetOps implementation for the hypothesis space point cloud widget. + + This widget shows the point cloud of the primary target pretrained model and + a point cloud of the hypothesis space. The widget listens to different topics + (e.g., `episode_number`, `step_number`, `hyp_color_button`, `hyp_scope_button`) + to determine which point cloud to show and how to color the points. + """ + + def __init__( + self, + plotter: Plotter, + data_parser: DataParser, + models_loader: PretrainedModelsLoader, + ): + self.plotter = plotter + self.data_parser = data_parser + self.models_loader = models_loader + + self.updaters = [ + WidgetUpdater( + topics=[ + TopicSpec("episode_number", required=True), + TopicSpec("step_number", required=True), + TopicSpec("model_button", required=True), + ], + callback=self.update_model, + ), + WidgetUpdater( + topics=[ + TopicSpec("episode_number", required=True), + TopicSpec("step_number", required=True), + TopicSpec("hyp_color_button", required=True), + TopicSpec("hyp_scope_button", required=True), + ], + callback=self.update_hypotheses, + ), + ] + + self._locators = self.create_locators() + self.hyp_space: Points | None = None + self.mlh_sphere: Sphere | None = None + + def create_locators(self) -> dict[str, DataLocator]: + locators = {} + locators["target"] = DataLocator( + path=[ + DataLocatorStep.key(name="episode"), + DataLocatorStep.key(name="lm", value="target"), + ] + ) + + locators["telemetry"] = DataLocator( + path=[ + DataLocatorStep.key(name="episode"), + DataLocatorStep.key(name="lm", value="LM_0"), + DataLocatorStep.key( + name="telemetry", value="hypotheses_updater_telemetry" + ), + DataLocatorStep.index(name="step"), + DataLocatorStep.key(name="obj"), + DataLocatorStep.key(name="channel", value="patch"), + ], + ) + return locators + + def remove(self, widget: Mesh) -> None: + if widget is not None: + self.plotter.at(2).remove(widget) + + def update_model( + self, widget: Mesh | None, msgs: list[TopicMessage] + ) -> tuple[Mesh | None, bool]: + self.remove(widget) + msgs_dict = {msg.name: msg.value for msg in msgs} + episode_number = msgs_dict["episode_number"] + step_number = msgs_dict["step_number"] + model_button = msgs_dict["model_button"] + + if model_button == "Model": + locator = self._locators["target"] + target = self.data_parser.extract(locator, episode=str(episode_number)) + target_id = target["primary_target_object"] + target_rot = target["primary_target_rotation_euler"] + target_pos = target["primary_target_position"] + + widget = self.models_loader.create_model(target_id).clone(deep=True) + widget.rotate_x(target_rot[0]) + widget.rotate_y(target_rot[1]) + widget.rotate_z(target_rot[2]) + + self.plotter.at(2).add(widget) + + self.plotter.at(2).render() + return widget, False + + def _clear_hyp_space(self) -> None: + if self.hyp_space is not None: + self.plotter.at(2).remove(self.hyp_space) + self.hyp_space = None + + if self.mlh_sphere is not None: + self.plotter.at(2).remove(self.mlh_sphere) + self.mlh_sphere = None + + def _extract_obj_telemetry( + self, + episode_number: int, + step_number: int, + object_id: str, + ) -> tuple[np.ndarray, np.ndarray, np.ndarray, np.ndarray, np.ndarray]: + obj_hyps_telemetry = self.data_parser.extract( + self._locators["telemetry"], + episode=str(episode_number), + step=step_number, + obj=object_id, + ) + evidences = obj_hyps_telemetry["evidence"] + locations = obj_hyps_telemetry["locations"] + pose_errors = obj_hyps_telemetry["pose_errors"] + ages = obj_hyps_telemetry["hypotheses_updater"]["ages"] + slopes = obj_hyps_telemetry["hypotheses_updater"]["evidence_slopes"] + + return evidences, locations, pose_errors, ages, slopes + + def _create_hyp_space( + self, + episode_number: int, + step_number: int, + hyp_color_button: str, + hyp_scope_button: str, + ) -> Points: + curr_object = self.data_parser.extract( + self._locators["target"], episode=str(episode_number) + )["primary_target_object"] + evidences, locations, pose_errors, ages, slopes = self._extract_obj_telemetry( + episode_number, step_number, curr_object + ) + pts = Points(np.array(locations), r=6, c="dg") + + if hyp_color_button == "Evidence": + pts.cmap("viridis", evidences, vmin=0.0) + pts.add_scalarbar(title="Evidence") + if hyp_color_button == "MLH": + mlh = np.zeros_like(evidences, dtype=float) + pts.cmap("viridis", mlh, vmin=0.0, vmax=1.0) + mlh_sphere = Sphere( + pos=locations[int(np.argmax(evidences))], r=0.002, c="yellow" + ) + self.mlh_sphere = mlh_sphere + self.plotter.at(2).add(mlh_sphere) + elif hyp_color_button == "Slope": + pts.cmap("viridis", slopes, vmin=0.0) + pts.add_scalarbar(title="Slope") + elif hyp_color_button == "Ages": + pts.cmap("viridis", ages, vmin=0.0) + pts.add_scalarbar(title="Age") + elif hyp_color_button == "Pose Error": + pts.cmap("viridis", pose_errors, vmin=0.0) + pts.add_scalarbar(title="Pose Error") + + return pts + + def update_hypotheses( + self, widget: Points, msgs: list[TopicMessage] + ) -> tuple[Points, bool]: + msgs_dict = {msg.name: msg.value for msg in msgs} + episode_number = msgs_dict["episode_number"] + step_number = msgs_dict["step_number"] + hyp_color_button = msgs_dict["hyp_color_button"] + hyp_scope_button = msgs_dict["hyp_scope_button"] + + self._clear_hyp_space() + + if hyp_scope_button != "No Hypotheses": + hyp_space = self._create_hyp_space( + episode_number, step_number, hyp_color_button, hyp_scope_button + ) + self.plotter.at(2).add(hyp_space) + self.hyp_space = hyp_space + + self.plotter.at(2).render() + return widget, False + + +class ModelButtonWidgetOps: + """WidgetOps implementation for showing/hiding the pretrained model point cloud.""" + + def __init__(self, plotter: Plotter): + self.plotter = plotter + + self._add_kwargs = { + "pos": (0.6, 0.98), + "states": ["Model", "No Model"], + "c": ["w", "w"], + "bc": ["dg", "dr"], + "size": 30, + "font": "Calco", + "bold": True, + } + + def add(self, callback: Callable) -> Button: + widget = self.plotter.at(0).add_button(callback, **self._add_kwargs) + self.plotter.at(0).render() + return widget + + def extract_state(self, widget: Button) -> str: + return extract_button_state(widget) + + def set_state(self, widget: Button, value: str) -> None: + set_button_state(widget, value) + + def state_to_messages(self, state: str) -> Iterable[TopicMessage]: + messages = [ + TopicMessage(name="model_button", value=state), + ] + return messages + + +class HypScopeButtonWidgetOps: + """WidgetOps implementation for showing/hiding the hypothesis space.""" + + def __init__(self, plotter: Plotter): + self.plotter = plotter + + self._add_kwargs = { + "pos": (0.85, 0.98), + "states": ["No Hypotheses", "Hypotheses"], + "c": ["w", "w"], + "bc": ["dr", "dg"], + "size": 30, + "font": "Calco", + "bold": True, + } + + def add(self, callback: Callable) -> Button: + widget = self.plotter.at(0).add_button(callback, **self._add_kwargs) + self.plotter.at(0).render() + return widget + + def extract_state(self, widget: Button) -> str: + return extract_button_state(widget) + + def set_state(self, widget: Button, value: str) -> None: + set_button_state(widget, value) + + def state_to_messages(self, state: str) -> Iterable[TopicMessage]: + messages = [ + TopicMessage(name="hyp_scope_button", value=state), + ] + return messages + + +class HypColorButtonWidgetOps: + """WidgetOps implementation for selecting how to color the hypothesis space. + + Note that this widget is hidden (by moving it outside of the scene) when the + hypothesis space is deactivated. It listens to `hyp_scope_button` to know if the + widget should be shown or hidden. + """ + + def __init__(self, plotter: Plotter): + self.plotter = plotter + + self._add_kwargs = { + "pos": (0.18, 0.15), + "states": ["None", "Evidence", "MLH", "Pose Error", "Slope", "Ages"], + "c": ["w", "w", "w", "w", "w", "w"], + "bc": ["gray", "dg", "ds", "dm", "db", "dc"], + "size": 30, + "font": "Calco", + "bold": True, + } + + self.updaters = [ + WidgetUpdater( + topics=[ + TopicSpec("hyp_scope_button", required=True), + ], + callback=self.toggle_button, + ), + ] + + def add(self, callback: Callable) -> Button: + widget = self.plotter.at(2).add_button(callback, **self._add_kwargs) + self.plotter.at(2).render() + return widget + + def extract_state(self, widget: Button) -> str: + return extract_button_state(widget) + + def set_state(self, widget: Button, value: str) -> None: + set_button_state(widget, value) + + def state_to_messages(self, state: str) -> Iterable[TopicMessage]: + messages = [ + TopicMessage(name="hyp_color_button", value=state), + ] + return messages + + def toggle_button( + self, widget: Button, msgs: list[TopicMessage] + ) -> tuple[Button, bool]: + msgs_dict = {msg.name: msg.value for msg in msgs} + + if msgs_dict["hyp_scope_button"] == "No Hypotheses": + # Hide the button by moving it outside of the scene + widget.SetPosition(-10.0, -10.0) + elif msgs_dict["hyp_scope_button"] == "Hypotheses": + x, y = self._add_kwargs["pos"] + widget.SetPosition(x, y) + + self.plotter.at(2).render() + + return widget, False + + +class LinePlotWidgetOps: + """WidgetOps implementation for the line plot. + + This widget shows a line plot for the max global slope and hypothesis space size + over time. It also shows the sampling bursts locations as vertical dashed red lines. + The current step is shown as a vertical solid black line and moves with the step + slider control. + """ + + def __init__( + self, + plotter: Plotter, + data_parser: DataParser, + step_mapper: StepMapper, + ): + self.plotter = plotter + self.data_parser = data_parser + self.step_mapper = step_mapper + self.updaters = [ + WidgetUpdater( + topics=[ + TopicSpec("episode_number", required=True), + TopicSpec("step_number", required=True), + ], + callback=self.update_plot, + ), + ] + self._locators = self.create_locators() + self.df = self._create_df() + + def create_locators(self) -> dict[str, DataLocator]: + locators = {} + + base_loc = DataLocator( + path=[ + DataLocatorStep.key(name="episode"), + DataLocatorStep.key(name="lm", value="LM_0"), + DataLocatorStep.key( + name="telemetry", value="hypotheses_updater_telemetry" + ), + DataLocatorStep.index(name="step"), + DataLocatorStep.key(name="obj"), + DataLocatorStep.key(name="channel", value="patch"), + ] + ) + + locators["target"] = DataLocator( + path=[ + DataLocatorStep.key(name="episode"), + DataLocatorStep.key(name="lm", value="target"), + DataLocatorStep.key(name="target_stat", value="primary_target_object"), + ] + ) + + locators["evidence"] = base_loc.extend( + [ + DataLocatorStep.key(name="telemetry2", value="evidence"), + ] + ) + + locators["max_slope"] = base_loc.extend( + [ + DataLocatorStep.key(name="telemetry2", value="hypotheses_updater"), + DataLocatorStep.key(name="telemetry3", value="max_slope"), + ] + ) + + locators["added_ids"] = base_loc.extend( + [ + DataLocatorStep.key(name="telemetry2", value="hypotheses_updater"), + DataLocatorStep.key(name="telemetry3", value="added_ids"), + ] + ) + + return locators + + def remove(self, widget: Mesh) -> None: + if widget is not None: + self.plotter.at(0).remove(widget) + + def _get_bursts(self) -> list[bool]: + bursts = [] + for episode in self.data_parser.query(self._locators["evidence"]): + obj = self.data_parser.extract(self._locators["target"], episode=episode) + for step in self.data_parser.query( + self._locators["added_ids"], episode=episode + ): + num_added = len( + self.data_parser.extract( + self._locators["added_ids"], + episode=episode, + step=step, + obj=obj, + ) + ) + bursts.append(num_added > 0) + return bursts + + def _get_hyp_space_sizes(self) -> list[int]: + hyp_space_sizes = [] + for episode in self.data_parser.query(self._locators["evidence"]): + obj = self.data_parser.extract(self._locators["target"], episode=episode) + for step in self.data_parser.query( + self._locators["evidence"], episode=episode + ): + num_hyp = len( + self.data_parser.extract( + self._locators["evidence"], + episode=episode, + step=step, + obj=obj, + ) + ) + hyp_space_sizes.append(num_hyp) + return hyp_space_sizes + + def _get_max_slopes(self) -> list[float]: + max_slopes = [] + for episode in self.data_parser.query(self._locators["evidence"]): + obj = self.data_parser.extract(self._locators["target"], episode=episode) + for step in self.data_parser.query( + self._locators["evidence"], episode=episode + ): + slope = self.data_parser.extract( + self._locators["max_slope"], + episode=episode, + step=step, + obj=obj, + ) + + max_slopes.append(slope) + return max_slopes + + def _create_df(self) -> pd.DataFrame: + bursts = self._get_bursts() + hyp_space_sizes = self._get_hyp_space_sizes() + max_slopes = self._get_max_slopes() + + if not (len(bursts) == len(hyp_space_sizes) == len(max_slopes)): + raise ValueError( + f"Length mismatch: bursts={len(bursts)}, " + f"sizes={len(hyp_space_sizes)}, slopes={len(max_slopes)}" + ) + + return pd.DataFrame( + { + "burst": bursts, + "hyp_space_size": hyp_space_sizes, + "max_slope": max_slopes, + } + ) + + def _create_burst_figure(self, global_step: int) -> plt.Figure: + df = self.df.copy() + + slopes = df["max_slope"].to_numpy(dtype=float) + hyp_space_sizes = df["hyp_space_size"].to_numpy(dtype=float) + bursts = df["burst"].to_numpy(dtype=bool) + + x = np.arange(len(slopes)) + + fig, ax_left = plt.subplots(1, 1, figsize=(14, 3)) + ax_right = ax_left.twinx() + + valid_idx = np.where(~np.isnan(slopes))[0] + start_idx = int(valid_idx[0]) if valid_idx.size > 0 else 0 + + if start_idx < len(slopes): + ax_left.plot(x[start_idx:], slopes[start_idx:], label="Max slope") + + ax_right.plot(x, hyp_space_sizes, linestyle="--", label="Hypothesis space size") + + ax_left.set_ylim(-1.0, 2.0) + ax_right.set_ylim(0, 10000) + + # Burst locations (red dashed lines) + add_idx = np.flatnonzero(bursts) + if add_idx.size > 0: + ymin, ymax = ax_left.get_ylim() + ax_left.vlines( + add_idx, + ymin, + ymax, + colors="red", + linestyles="--", + alpha=1.0, + linewidth=1.0, + zorder=1, + label="Burst", + ) + + # Current step marker (single black vertical line) + if 0 <= global_step < len(slopes): + ymin, ymax = ax_left.get_ylim() + ax_left.vlines( + global_step, + ymin, + ymax, + colors="black", + linestyles="-", + linewidth=1.5, + zorder=2, + label="Current step", + ) + + ax_left.set_ylabel("Max slope") + ax_right.set_ylabel("Hyp space size") + + # Merge legends and place above + lines_left, labels_left = ax_left.get_legend_handles_labels() + lines_right, labels_right = ax_right.get_legend_handles_labels() + all_lines = lines_left + lines_right + all_labels = labels_left + labels_right + + if all_lines: + ax_left.legend( + all_lines, + all_labels, + loc="upper center", + bbox_to_anchor=(0.5, 1.20), + ncol=len(all_lines), + frameon=False, + columnspacing=1.0, + handletextpad=0.5, + ) + + fig.tight_layout(rect=[0, 0, 1, 0.90]) + + widget = Image(fig) + plt.close(fig) + return widget + + def update_plot( + self, widget: Image, msgs: list[TopicMessage] + ) -> tuple[Image, bool]: + self.remove(widget) + msgs_dict = {msg.name: msg.value for msg in msgs} + episode_number = msgs_dict["episode_number"] + step_number = msgs_dict["step_number"] + global_step = self.step_mapper.local_to_global(episode_number, step_number) + + widget = self._create_burst_figure(global_step) + widget.pos(-400, -150, 0) + + self.plotter.at(0).add(widget) + self.plotter.at(0).render() + + return widget, False + + +class ClickWidgetOps: + """Captures 3D click positions and publish them on the bus. + + This class registers plotter-level mouse callbacks. A right-click + resets the camera pose. There is no visual widget created by this class. + + Attributes: + plotter: The `vedo.Plotter` where callbacks are installed. + cam_dict: Dictionary for camera default specs. + click_location: Last picked 3D location, if any. + _on_change_cb: The widget callback to invoke on left-click. + """ + + def __init__(self, plotter: Plotter, cam_dict: dict[str, Any]) -> None: + self.plotter = plotter + self.cam_dict = cam_dict + self.click_location: Location3D + self._on_change_cb: Callable + + def add(self, callback: Callable) -> None: + """Register mouse callbacks on the plotter. + + Note that this callback makes use of the `VtkDebounceScheduler` + to publish messages. Storing the callback and triggering it, will + simulate a UI change on e.g., a button or a slider, which schedules + a publish. We use this callback because this event is not triggered + by receiving topics from a `WidgetUpdater`. + + + Args: + callback: Function invoked like `(widget, event)` when a left-click + captures a 3D location. + """ + self._on_change_cb = callback + self.plotter.at(0).add_callback("RightButtonPress", self.on_right_click) + + def align_camera(self, cam_a: Any, cam_b: Any) -> None: + """Align the camera objects.""" + cam_a.SetPosition(cam_b.GetPosition()) + cam_a.SetFocalPoint(cam_b.GetFocalPoint()) + cam_a.SetViewUp(cam_b.GetViewUp()) + cam_a.SetClippingRange(cam_b.GetClippingRange()) + cam_a.SetParallelScale(cam_b.GetParallelScale()) + + def on_right_click(self, event) -> None: + """Handle right mouse press (reset camera pose and render). + + Notes: + Bound to the "RightButtonPress" event in `self.add()`. + """ + if event.at == 0: + renderer = self.plotter.at(0).renderer + if renderer is not None: + cam = renderer.GetActiveCamera() + cam.SetPosition(self.cam_dict["pos"]) + cam.SetFocalPoint(self.cam_dict["focal_point"]) + cam.SetViewUp((0, 1, 0)) + cam.SetClippingRange((0.01, 1000.01)) + self.plotter.at(0).render() + elif event.at == 1: + cam_clicked = self.plotter.renderers[1].GetActiveCamera() + cam_copy = self.plotter.renderers[2].GetActiveCamera() + self.align_camera(cam_copy, cam_clicked) + elif event.at == 2: + cam_clicked = self.plotter.renderers[1].GetActiveCamera() + cam_copy = self.plotter.renderers[2].GetActiveCamera() + self.align_camera(cam_clicked, cam_copy) + + +class InteractivePlot: + """An interactive plot for hypotheses and sampling bursts location. + + This visualizations features the following: + - A plot of the primary target mesh (with alpha transparency slider) with the agent + and patch location on the object. + - Buttons to activate or deactivate the history of agent and/or patch locations. + - A plot of the pretrained model for the primary target with a button to show the + hypothesis space for this object. + - The hypothesis space pointcloud can be coloured by different metrics, such as + evidence, mlh, slope, pose error or age. + - A line plot showing the maximum global slope (burst trigger), the + location/duration of the sampling bursts and the hypothesis space size over + time. + + Args: + exp_path: Path to the experiment log consumed by `DataParser`. + data_path: Root directory containing YCB meshes for `YCBMeshLoader`. + models_path: Path to the pretrained models pt file. + + Attributes: + data_parser: Parser that reads the JSON log file and serves queries. + step_mapper: Mapper between global step index and local step index (with episode + number). + ycb_loader: Loader that provides textured YCB meshes. + event_bus: Publisher used to route `TopicMessage` events among widgets. + plotter: Vedo `Plotter` hosting all widgets. + scheduler: Debounce scheduler bound to the plotter interactor. + _widgets: Mapping of widget names to their `Widget` instances. It + includes episode and step sliders, primary/prev/next buttons, an + age-threshold slider, the correlation plot, and mesh viewers. + + """ + + def __init__( + self, + exp_path: str, + data_path: str, + models_path: str, + ): + renderer_areas = [ + {"bottomleft": (0.0, 0.0), "topright": (1.0, 1.0)}, + {"bottomleft": (0.05, 0.5), "topright": (0.49, 0.9)}, + {"bottomleft": (0.51, 0.5), "topright": (0.95, 0.9)}, + ] + + self.axes_dict = { + "xrange": (-0.05, 0.05), + "yrange": (1.45, 1.55), + "zrange": (-0.05, 0.05), + } + self.cam_dict = {"pos": (300, 200, 1500), "focal_point": (300, 200, 0)} + + self.data_parser = DataParser(exp_path) + self.step_mapper = StepMapper(self.data_parser) + self.ycb_loader = YCBMeshLoader(data_path) + self.models_loader = PretrainedModelsLoader(models_path) + self.event_bus = Publisher() + self.plotter = Plotter(shape=renderer_areas, sharecam=False).render() + self.scheduler = VtkDebounceScheduler(self.plotter.interactor, period_ms=33) + + # create and add the widgets to the plotter + self._widgets = self.create_widgets() + for w in self._widgets.values(): + w.add() + self._widgets["step_slider"].set_state(0) + self._widgets["sensor_path_button"].set_state("No Sensor Path") + self._widgets["patch_path_button"].set_state("No Patch Path") + self._widgets["alpha_slider"].set_state(1.0) + self._widgets["model_button"].set_state("Model") + self._widgets["hyp_color_button"].set_state("None") + self._widgets["hyp_scope_button"].set_state("No Hypotheses") + + self.plotter.at(0).show( + camera=deepcopy(self.cam_dict), + interactive=False, # Must be set to False if not the last `show` call + resetcam=False, + ) + self.plotter.at(1).show( + axes=deepcopy(self.axes_dict), + interactive=False, # Must be set to False if not the last `show` call + resetcam=True, + ) + + self.plotter.at(2).show( + axes=deepcopy(self.axes_dict), + interactive=True, # Must be set to True on the last `show` call + resetcam=True, + ) + + # === No code runs after the last interactive call === # + + def create_widgets(self): + widgets = {} + + widgets["step_slider"] = Widget[Slider2D, int]( + widget_ops=StepSliderWidgetOps( + plotter=self.plotter, + data_parser=self.data_parser, + step_mapper=self.step_mapper, + ), + bus=self.event_bus, + scheduler=self.scheduler, + debounce_sec=0.1, + dedupe=True, + ) + + widgets["primary_mesh"] = Widget[Mesh, None]( + widget_ops=GtMeshWidgetOps( + plotter=self.plotter, + data_parser=self.data_parser, + ycb_loader=self.ycb_loader, + ), + bus=self.event_bus, + scheduler=self.scheduler, + debounce_sec=0.5, + dedupe=True, + ) + + widgets["alpha_slider"] = Widget[Slider2D, float]( + widget_ops=AlphaSliderWidgetOps( + plotter=self.plotter, + ), + bus=self.event_bus, + scheduler=self.scheduler, + debounce_sec=0.1, + dedupe=True, + ) + + widgets["sensor_path_button"] = Widget[Button, str]( + widget_ops=SensorPathButtonWidgetOps(plotter=self.plotter), + bus=self.event_bus, + scheduler=self.scheduler, + debounce_sec=0.2, + dedupe=True, + ) + + widgets["patch_path_button"] = Widget[Button, str]( + widget_ops=PatchPathButtonWidgetOps(plotter=self.plotter), + bus=self.event_bus, + scheduler=self.scheduler, + debounce_sec=0.2, + dedupe=True, + ) + + widgets["hyp_space_viz"] = Widget[Mesh, None]( + widget_ops=HypSpaceWidgetOps( + plotter=self.plotter, + data_parser=self.data_parser, + models_loader=self.models_loader, + ), + bus=self.event_bus, + scheduler=self.scheduler, + debounce_sec=0.5, + dedupe=True, + ) + + widgets["model_button"] = Widget[Button, str]( + widget_ops=ModelButtonWidgetOps(plotter=self.plotter), + bus=self.event_bus, + scheduler=self.scheduler, + debounce_sec=0.2, + dedupe=True, + ) + + widgets["hyp_color_button"] = Widget[Button, str]( + widget_ops=HypColorButtonWidgetOps(plotter=self.plotter), + bus=self.event_bus, + scheduler=self.scheduler, + debounce_sec=0.2, + dedupe=True, + ) + + widgets["hyp_scope_button"] = Widget[Button, str]( + widget_ops=HypScopeButtonWidgetOps(plotter=self.plotter), + bus=self.event_bus, + scheduler=self.scheduler, + debounce_sec=0.2, + dedupe=True, + ) + + widgets["line_plot"] = Widget[Image, str]( + widget_ops=LinePlotWidgetOps( + plotter=self.plotter, + data_parser=self.data_parser, + step_mapper=self.step_mapper, + ), + bus=self.event_bus, + scheduler=self.scheduler, + debounce_sec=0.2, + dedupe=True, + ) + + widgets["click_widget"] = Widget[None, Location3D]( + widget_ops=ClickWidgetOps( + plotter=self.plotter, cam_dict=deepcopy(self.cam_dict) + ), + bus=self.event_bus, + scheduler=self.scheduler, + debounce_sec=0.1, + dedupe=True, + ) + + return widgets + + +@register( + "interactive_hypothesis_space_pointcloud", + description="Pointcloud of hypothesis space and lineplot of sampling bursts", +) +def main( + experiment_log_dir: str, objects_mesh_dir: str, pretrained_models_file: str +) -> int: + """Interactive visualization for inspecting the primary target hypothesis space. + + This plot also allows for inspecting the sampling bursts and their slope triggers in + a line plot. + + Args: + experiment_log_dir: Path to the experiment directory containing the detailed + stats file. + objects_mesh_dir: Path to the root directory of YCB object meshes. + pretrained_models_file: Path to the pretrained models pt file. + + Returns: + Exit code. + """ + if not Path(experiment_log_dir).exists(): + logger.error(f"Experiment path not found: {experiment_log_dir}") + return 1 + + data_path = str(Path(objects_mesh_dir).expanduser()) + models_path = str(Path(pretrained_models_file).expanduser()) + + InteractivePlot(experiment_log_dir, data_path, models_path) + + return 0 + + +@attach_args("interactive_hypothesis_space_pointcloud") +def add_arguments(p: argparse.ArgumentParser) -> None: + p.add_argument( + "experiment_log_dir", + help=( + "The directory containing the experiment log with the detailed stats file." + ), + ) + p.add_argument( + "--objects_mesh_dir", + default="~/tbp/data/habitat/objects/ycb/meshes", + help=("The directory containing the mesh objects."), + ) + + p.add_argument( + "--pretrained_models_file", + default="~/tbp/results/monty/pretrained_models/pretrained_ycb_v11/surf_agent_1lm_10distinctobj/pretrained/model.pt", + help=("The file containing the pretrained models."), + ) diff --git a/uv.lock b/uv.lock index f04282d..773e988 100644 --- a/uv.lock +++ b/uv.lock @@ -1,7 +1,155 @@ version = 1 -revision = 2 +revision = 3 requires-python = ">=3.13, <4" +[[package]] +name = "aiohappyeyeballs" +version = "2.6.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/26/30/f84a107a9c4331c14b2b586036f40965c128aa4fee4dda5d3d51cb14ad54/aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558", size = 22760, upload-time = "2025-03-12T01:42:48.764Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0f/15/5bf3b99495fb160b63f95972b81750f18f7f4e02ad051373b669d17d44f2/aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8", size = 15265, upload-time = "2025-03-12T01:42:47.083Z" }, +] + +[[package]] +name = "aiohttp" +version = "3.13.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "aiohappyeyeballs" }, + { name = "aiosignal" }, + { name = "attrs" }, + { name = "frozenlist" }, + { name = "multidict" }, + { name = "propcache" }, + { name = "yarl" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/1c/ce/3b83ebba6b3207a7135e5fcaba49706f8a4b6008153b4e30540c982fae26/aiohttp-3.13.2.tar.gz", hash = "sha256:40176a52c186aefef6eb3cad2cdd30cd06e3afbe88fe8ab2af9c0b90f228daca", size = 7837994, upload-time = "2025-10-28T20:59:39.937Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bf/78/7e90ca79e5aa39f9694dcfd74f4720782d3c6828113bb1f3197f7e7c4a56/aiohttp-3.13.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:7519bdc7dfc1940d201651b52bf5e03f5503bda45ad6eacf64dda98be5b2b6be", size = 732139, upload-time = "2025-10-28T20:57:02.455Z" }, + { url = "https://files.pythonhosted.org/packages/db/ed/1f59215ab6853fbaa5c8495fa6cbc39edfc93553426152b75d82a5f32b76/aiohttp-3.13.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:088912a78b4d4f547a1f19c099d5a506df17eacec3c6f4375e2831ec1d995742", size = 490082, upload-time = "2025-10-28T20:57:04.784Z" }, + { url = "https://files.pythonhosted.org/packages/68/7b/fe0fe0f5e05e13629d893c760465173a15ad0039c0a5b0d0040995c8075e/aiohttp-3.13.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5276807b9de9092af38ed23ce120539ab0ac955547b38563a9ba4f5b07b95293", size = 489035, upload-time = "2025-10-28T20:57:06.894Z" }, + { url = "https://files.pythonhosted.org/packages/d2/04/db5279e38471b7ac801d7d36a57d1230feeee130bbe2a74f72731b23c2b1/aiohttp-3.13.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1237c1375eaef0db4dcd7c2559f42e8af7b87ea7d295b118c60c36a6e61cb811", size = 1720387, upload-time = "2025-10-28T20:57:08.685Z" }, + { url = "https://files.pythonhosted.org/packages/31/07/8ea4326bd7dae2bd59828f69d7fdc6e04523caa55e4a70f4a8725a7e4ed2/aiohttp-3.13.2-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:96581619c57419c3d7d78703d5b78c1e5e5fc0172d60f555bdebaced82ded19a", size = 1688314, upload-time = "2025-10-28T20:57:10.693Z" }, + { url = "https://files.pythonhosted.org/packages/48/ab/3d98007b5b87ffd519d065225438cc3b668b2f245572a8cb53da5dd2b1bc/aiohttp-3.13.2-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a2713a95b47374169409d18103366de1050fe0ea73db358fc7a7acb2880422d4", size = 1756317, upload-time = "2025-10-28T20:57:12.563Z" }, + { url = "https://files.pythonhosted.org/packages/97/3d/801ca172b3d857fafb7b50c7c03f91b72b867a13abca982ed6b3081774ef/aiohttp-3.13.2-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:228a1cd556b3caca590e9511a89444925da87d35219a49ab5da0c36d2d943a6a", size = 1858539, upload-time = "2025-10-28T20:57:14.623Z" }, + { url = "https://files.pythonhosted.org/packages/f7/0d/4764669bdf47bd472899b3d3db91fffbe925c8e3038ec591a2fd2ad6a14d/aiohttp-3.13.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ac6cde5fba8d7d8c6ac963dbb0256a9854e9fafff52fbcc58fdf819357892c3e", size = 1739597, upload-time = "2025-10-28T20:57:16.399Z" }, + { url = "https://files.pythonhosted.org/packages/c4/52/7bd3c6693da58ba16e657eb904a5b6decfc48ecd06e9ac098591653b1566/aiohttp-3.13.2-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f2bef8237544f4e42878c61cef4e2839fee6346dc60f5739f876a9c50be7fcdb", size = 1555006, upload-time = "2025-10-28T20:57:18.288Z" }, + { url = "https://files.pythonhosted.org/packages/48/30/9586667acec5993b6f41d2ebcf96e97a1255a85f62f3c653110a5de4d346/aiohttp-3.13.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:16f15a4eac3bc2d76c45f7ebdd48a65d41b242eb6c31c2245463b40b34584ded", size = 1683220, upload-time = "2025-10-28T20:57:20.241Z" }, + { url = "https://files.pythonhosted.org/packages/71/01/3afe4c96854cfd7b30d78333852e8e851dceaec1c40fd00fec90c6402dd2/aiohttp-3.13.2-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:bb7fb776645af5cc58ab804c58d7eba545a97e047254a52ce89c157b5af6cd0b", size = 1712570, upload-time = "2025-10-28T20:57:22.253Z" }, + { url = "https://files.pythonhosted.org/packages/11/2c/22799d8e720f4697a9e66fd9c02479e40a49de3de2f0bbe7f9f78a987808/aiohttp-3.13.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:e1b4951125ec10c70802f2cb09736c895861cd39fd9dcb35107b4dc8ae6220b8", size = 1733407, upload-time = "2025-10-28T20:57:24.37Z" }, + { url = "https://files.pythonhosted.org/packages/34/cb/90f15dd029f07cebbd91f8238a8b363978b530cd128488085b5703683594/aiohttp-3.13.2-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:550bf765101ae721ee1d37d8095f47b1f220650f85fe1af37a90ce75bab89d04", size = 1550093, upload-time = "2025-10-28T20:57:26.257Z" }, + { url = "https://files.pythonhosted.org/packages/69/46/12dce9be9d3303ecbf4d30ad45a7683dc63d90733c2d9fe512be6716cd40/aiohttp-3.13.2-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:fe91b87fc295973096251e2d25a811388e7d8adf3bd2b97ef6ae78bc4ac6c476", size = 1758084, upload-time = "2025-10-28T20:57:28.349Z" }, + { url = "https://files.pythonhosted.org/packages/f9/c8/0932b558da0c302ffd639fc6362a313b98fdf235dc417bc2493da8394df7/aiohttp-3.13.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e0c8e31cfcc4592cb200160344b2fb6ae0f9e4effe06c644b5a125d4ae5ebe23", size = 1716987, upload-time = "2025-10-28T20:57:30.233Z" }, + { url = "https://files.pythonhosted.org/packages/5d/8b/f5bd1a75003daed099baec373aed678f2e9b34f2ad40d85baa1368556396/aiohttp-3.13.2-cp313-cp313-win32.whl", hash = "sha256:0740f31a60848d6edb296a0df827473eede90c689b8f9f2a4cdde74889eb2254", size = 425859, upload-time = "2025-10-28T20:57:32.105Z" }, + { url = "https://files.pythonhosted.org/packages/5d/28/a8a9fc6957b2cee8902414e41816b5ab5536ecf43c3b1843c10e82c559b2/aiohttp-3.13.2-cp313-cp313-win_amd64.whl", hash = "sha256:a88d13e7ca367394908f8a276b89d04a3652044612b9a408a0bb22a5ed976a1a", size = 452192, upload-time = "2025-10-28T20:57:34.166Z" }, + { url = "https://files.pythonhosted.org/packages/9b/36/e2abae1bd815f01c957cbf7be817b3043304e1c87bad526292a0410fdcf9/aiohttp-3.13.2-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:2475391c29230e063ef53a66669b7b691c9bfc3f1426a0f7bcdf1216bdbac38b", size = 735234, upload-time = "2025-10-28T20:57:36.415Z" }, + { url = "https://files.pythonhosted.org/packages/ca/e3/1ee62dde9b335e4ed41db6bba02613295a0d5b41f74a783c142745a12763/aiohttp-3.13.2-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:f33c8748abef4d8717bb20e8fb1b3e07c6adacb7fd6beaae971a764cf5f30d61", size = 490733, upload-time = "2025-10-28T20:57:38.205Z" }, + { url = "https://files.pythonhosted.org/packages/1a/aa/7a451b1d6a04e8d15a362af3e9b897de71d86feac3babf8894545d08d537/aiohttp-3.13.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:ae32f24bbfb7dbb485a24b30b1149e2f200be94777232aeadba3eecece4d0aa4", size = 491303, upload-time = "2025-10-28T20:57:40.122Z" }, + { url = "https://files.pythonhosted.org/packages/57/1e/209958dbb9b01174870f6a7538cd1f3f28274fdbc88a750c238e2c456295/aiohttp-3.13.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5d7f02042c1f009ffb70067326ef183a047425bb2ff3bc434ead4dd4a4a66a2b", size = 1717965, upload-time = "2025-10-28T20:57:42.28Z" }, + { url = "https://files.pythonhosted.org/packages/08/aa/6a01848d6432f241416bc4866cae8dc03f05a5a884d2311280f6a09c73d6/aiohttp-3.13.2-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:93655083005d71cd6c072cdab54c886e6570ad2c4592139c3fb967bfc19e4694", size = 1667221, upload-time = "2025-10-28T20:57:44.869Z" }, + { url = "https://files.pythonhosted.org/packages/87/4f/36c1992432d31bbc789fa0b93c768d2e9047ec8c7177e5cd84ea85155f36/aiohttp-3.13.2-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:0db1e24b852f5f664cd728db140cf11ea0e82450471232a394b3d1a540b0f906", size = 1757178, upload-time = "2025-10-28T20:57:47.216Z" }, + { url = "https://files.pythonhosted.org/packages/ac/b4/8e940dfb03b7e0f68a82b88fd182b9be0a65cb3f35612fe38c038c3112cf/aiohttp-3.13.2-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b009194665bcd128e23eaddef362e745601afa4641930848af4c8559e88f18f9", size = 1838001, upload-time = "2025-10-28T20:57:49.337Z" }, + { url = "https://files.pythonhosted.org/packages/d7/ef/39f3448795499c440ab66084a9db7d20ca7662e94305f175a80f5b7e0072/aiohttp-3.13.2-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c038a8fdc8103cd51dbd986ecdce141473ffd9775a7a8057a6ed9c3653478011", size = 1716325, upload-time = "2025-10-28T20:57:51.327Z" }, + { url = "https://files.pythonhosted.org/packages/d7/51/b311500ffc860b181c05d91c59a1313bdd05c82960fdd4035a15740d431e/aiohttp-3.13.2-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:66bac29b95a00db411cd758fea0e4b9bdba6d549dfe333f9a945430f5f2cc5a6", size = 1547978, upload-time = "2025-10-28T20:57:53.554Z" }, + { url = "https://files.pythonhosted.org/packages/31/64/b9d733296ef79815226dab8c586ff9e3df41c6aff2e16c06697b2d2e6775/aiohttp-3.13.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:4ebf9cfc9ba24a74cf0718f04aac2a3bbe745902cc7c5ebc55c0f3b5777ef213", size = 1682042, upload-time = "2025-10-28T20:57:55.617Z" }, + { url = "https://files.pythonhosted.org/packages/3f/30/43d3e0f9d6473a6db7d472104c4eff4417b1e9df01774cb930338806d36b/aiohttp-3.13.2-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:a4b88ebe35ce54205c7074f7302bd08a4cb83256a3e0870c72d6f68a3aaf8e49", size = 1680085, upload-time = "2025-10-28T20:57:57.59Z" }, + { url = "https://files.pythonhosted.org/packages/16/51/c709f352c911b1864cfd1087577760ced64b3e5bee2aa88b8c0c8e2e4972/aiohttp-3.13.2-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:98c4fb90bb82b70a4ed79ca35f656f4281885be076f3f970ce315402b53099ae", size = 1728238, upload-time = "2025-10-28T20:57:59.525Z" }, + { url = "https://files.pythonhosted.org/packages/19/e2/19bd4c547092b773caeb48ff5ae4b1ae86756a0ee76c16727fcfd281404b/aiohttp-3.13.2-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:ec7534e63ae0f3759df3a1ed4fa6bc8f75082a924b590619c0dd2f76d7043caa", size = 1544395, upload-time = "2025-10-28T20:58:01.914Z" }, + { url = "https://files.pythonhosted.org/packages/cf/87/860f2803b27dfc5ed7be532832a3498e4919da61299b4a1f8eb89b8ff44d/aiohttp-3.13.2-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:5b927cf9b935a13e33644cbed6c8c4b2d0f25b713d838743f8fe7191b33829c4", size = 1742965, upload-time = "2025-10-28T20:58:03.972Z" }, + { url = "https://files.pythonhosted.org/packages/67/7f/db2fc7618925e8c7a601094d5cbe539f732df4fb570740be88ed9e40e99a/aiohttp-3.13.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:88d6c017966a78c5265d996c19cdb79235be5e6412268d7e2ce7dee339471b7a", size = 1697585, upload-time = "2025-10-28T20:58:06.189Z" }, + { url = "https://files.pythonhosted.org/packages/0c/07/9127916cb09bb38284db5036036042b7b2c514c8ebaeee79da550c43a6d6/aiohttp-3.13.2-cp314-cp314-win32.whl", hash = "sha256:f7c183e786e299b5d6c49fb43a769f8eb8e04a2726a2bd5887b98b5cc2d67940", size = 431621, upload-time = "2025-10-28T20:58:08.636Z" }, + { url = "https://files.pythonhosted.org/packages/fb/41/554a8a380df6d3a2bba8a7726429a23f4ac62aaf38de43bb6d6cde7b4d4d/aiohttp-3.13.2-cp314-cp314-win_amd64.whl", hash = "sha256:fe242cd381e0fb65758faf5ad96c2e460df6ee5b2de1072fe97e4127927e00b4", size = 457627, upload-time = "2025-10-28T20:58:11Z" }, + { url = "https://files.pythonhosted.org/packages/c7/8e/3824ef98c039d3951cb65b9205a96dd2b20f22241ee17d89c5701557c826/aiohttp-3.13.2-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:f10d9c0b0188fe85398c61147bbd2a657d616c876863bfeff43376e0e3134673", size = 767360, upload-time = "2025-10-28T20:58:13.358Z" }, + { url = "https://files.pythonhosted.org/packages/a4/0f/6a03e3fc7595421274fa34122c973bde2d89344f8a881b728fa8c774e4f1/aiohttp-3.13.2-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:e7c952aefdf2460f4ae55c5e9c3e80aa72f706a6317e06020f80e96253b1accd", size = 504616, upload-time = "2025-10-28T20:58:15.339Z" }, + { url = "https://files.pythonhosted.org/packages/c6/aa/ed341b670f1bc8a6f2c6a718353d13b9546e2cef3544f573c6a1ff0da711/aiohttp-3.13.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c20423ce14771d98353d2e25e83591fa75dfa90a3c1848f3d7c68243b4fbded3", size = 509131, upload-time = "2025-10-28T20:58:17.693Z" }, + { url = "https://files.pythonhosted.org/packages/7f/f0/c68dac234189dae5c4bbccc0f96ce0cc16b76632cfc3a08fff180045cfa4/aiohttp-3.13.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e96eb1a34396e9430c19d8338d2ec33015e4a87ef2b4449db94c22412e25ccdf", size = 1864168, upload-time = "2025-10-28T20:58:20.113Z" }, + { url = "https://files.pythonhosted.org/packages/8f/65/75a9a76db8364b5d0e52a0c20eabc5d52297385d9af9c35335b924fafdee/aiohttp-3.13.2-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:23fb0783bc1a33640036465019d3bba069942616a6a2353c6907d7fe1ccdaf4e", size = 1719200, upload-time = "2025-10-28T20:58:22.583Z" }, + { url = "https://files.pythonhosted.org/packages/f5/55/8df2ed78d7f41d232f6bd3ff866b6f617026551aa1d07e2f03458f964575/aiohttp-3.13.2-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2e1a9bea6244a1d05a4e57c295d69e159a5c50d8ef16aa390948ee873478d9a5", size = 1843497, upload-time = "2025-10-28T20:58:24.672Z" }, + { url = "https://files.pythonhosted.org/packages/e9/e0/94d7215e405c5a02ccb6a35c7a3a6cfff242f457a00196496935f700cde5/aiohttp-3.13.2-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0a3d54e822688b56e9f6b5816fb3de3a3a64660efac64e4c2dc435230ad23bad", size = 1935703, upload-time = "2025-10-28T20:58:26.758Z" }, + { url = "https://files.pythonhosted.org/packages/0b/78/1eeb63c3f9b2d1015a4c02788fb543141aad0a03ae3f7a7b669b2483f8d4/aiohttp-3.13.2-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7a653d872afe9f33497215745da7a943d1dc15b728a9c8da1c3ac423af35178e", size = 1792738, upload-time = "2025-10-28T20:58:29.787Z" }, + { url = "https://files.pythonhosted.org/packages/41/75/aaf1eea4c188e51538c04cc568040e3082db263a57086ea74a7d38c39e42/aiohttp-3.13.2-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:56d36e80d2003fa3fc0207fac644216d8532e9504a785ef9a8fd013f84a42c61", size = 1624061, upload-time = "2025-10-28T20:58:32.529Z" }, + { url = "https://files.pythonhosted.org/packages/9b/c2/3b6034de81fbcc43de8aeb209073a2286dfb50b86e927b4efd81cf848197/aiohttp-3.13.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:78cd586d8331fb8e241c2dd6b2f4061778cc69e150514b39a9e28dd050475661", size = 1789201, upload-time = "2025-10-28T20:58:34.618Z" }, + { url = "https://files.pythonhosted.org/packages/c9/38/c15dcf6d4d890217dae79d7213988f4e5fe6183d43893a9cf2fe9e84ca8d/aiohttp-3.13.2-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:20b10bbfbff766294fe99987f7bb3b74fdd2f1a2905f2562132641ad434dcf98", size = 1776868, upload-time = "2025-10-28T20:58:38.835Z" }, + { url = "https://files.pythonhosted.org/packages/04/75/f74fd178ac81adf4f283a74847807ade5150e48feda6aef024403716c30c/aiohttp-3.13.2-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:9ec49dff7e2b3c85cdeaa412e9d438f0ecd71676fde61ec57027dd392f00c693", size = 1790660, upload-time = "2025-10-28T20:58:41.507Z" }, + { url = "https://files.pythonhosted.org/packages/e7/80/7368bd0d06b16b3aba358c16b919e9c46cf11587dc572091031b0e9e3ef0/aiohttp-3.13.2-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:94f05348c4406450f9d73d38efb41d669ad6cd90c7ee194810d0eefbfa875a7a", size = 1617548, upload-time = "2025-10-28T20:58:43.674Z" }, + { url = "https://files.pythonhosted.org/packages/7d/4b/a6212790c50483cb3212e507378fbe26b5086d73941e1ec4b56a30439688/aiohttp-3.13.2-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:fa4dcb605c6f82a80c7f95713c2b11c3b8e9893b3ebd2bc9bde93165ed6107be", size = 1817240, upload-time = "2025-10-28T20:58:45.787Z" }, + { url = "https://files.pythonhosted.org/packages/ff/f7/ba5f0ba4ea8d8f3c32850912944532b933acbf0f3a75546b89269b9b7dde/aiohttp-3.13.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:cf00e5db968c3f67eccd2778574cf64d8b27d95b237770aa32400bd7a1ca4f6c", size = 1762334, upload-time = "2025-10-28T20:58:47.936Z" }, + { url = "https://files.pythonhosted.org/packages/7e/83/1a5a1856574588b1cad63609ea9ad75b32a8353ac995d830bf5da9357364/aiohttp-3.13.2-cp314-cp314t-win32.whl", hash = "sha256:d23b5fe492b0805a50d3371e8a728a9134d8de5447dce4c885f5587294750734", size = 464685, upload-time = "2025-10-28T20:58:50.642Z" }, + { url = "https://files.pythonhosted.org/packages/9f/4d/d22668674122c08f4d56972297c51a624e64b3ed1efaa40187607a7cb66e/aiohttp-3.13.2-cp314-cp314t-win_amd64.whl", hash = "sha256:ff0a7b0a82a7ab905cbda74006318d1b12e37c797eb1b0d4eb3e316cf47f658f", size = 498093, upload-time = "2025-10-28T20:58:52.782Z" }, +] + +[[package]] +name = "aiosignal" +version = "1.4.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "frozenlist" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/61/62/06741b579156360248d1ec624842ad0edf697050bbaf7c3e46394e106ad1/aiosignal-1.4.0.tar.gz", hash = "sha256:f47eecd9468083c2029cc99945502cb7708b082c232f9aca65da147157b251c7", size = 25007, upload-time = "2025-07-03T22:54:43.528Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fb/76/641ae371508676492379f16e2fa48f4e2c11741bd63c48be4b12a6b09cba/aiosignal-1.4.0-py3-none-any.whl", hash = "sha256:053243f8b92b990551949e63930a839ff0cf0b0ebbe0597b0f3fb19e1a0fe82e", size = 7490, upload-time = "2025-07-03T22:54:42.156Z" }, +] + +[[package]] +name = "attrs" +version = "25.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6b/5c/685e6633917e101e5dcb62b9dd76946cbb57c26e133bae9e0cd36033c0a9/attrs-25.4.0.tar.gz", hash = "sha256:16d5969b87f0859ef33a48b35d55ac1be6e42ae49d5e853b597db70c35c57e11", size = 934251, upload-time = "2025-10-06T13:54:44.725Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3a/2a/7cc015f5b9f5db42b7d48157e23356022889fc354a2813c15934b7cb5c0e/attrs-25.4.0-py3-none-any.whl", hash = "sha256:adcf7e2a1fb3b36ac48d97835bb6d8ade15b8dcce26aba8bf1d14847b57a3373", size = 67615, upload-time = "2025-10-06T13:54:43.17Z" }, +] + +[[package]] +name = "certifi" +version = "2025.11.12" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a2/8c/58f469717fa48465e4a50c014a0400602d3c437d7c0c468e17ada824da3a/certifi-2025.11.12.tar.gz", hash = "sha256:d8ab5478f2ecd78af242878415affce761ca6bc54a22a27e026d7c25357c3316", size = 160538, upload-time = "2025-11-12T02:54:51.517Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/70/7d/9bc192684cea499815ff478dfcdc13835ddf401365057044fb721ec6bddb/certifi-2025.11.12-py3-none-any.whl", hash = "sha256:97de8790030bbd5c2d96b7ec782fc2f7820ef8dba6db909ccf95449f2d062d4b", size = 159438, upload-time = "2025-11-12T02:54:49.735Z" }, +] + +[[package]] +name = "charset-normalizer" +version = "3.4.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/13/69/33ddede1939fdd074bce5434295f38fae7136463422fe4fd3e0e89b98062/charset_normalizer-3.4.4.tar.gz", hash = "sha256:94537985111c35f28720e43603b8e7b43a6ecfb2ce1d3058bbe955b73404e21a", size = 129418, upload-time = "2025-10-14T04:42:32.879Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/97/45/4b3a1239bbacd321068ea6e7ac28875b03ab8bc0aa0966452db17cd36714/charset_normalizer-3.4.4-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:e1f185f86a6f3403aa2420e815904c67b2f9ebc443f045edd0de921108345794", size = 208091, upload-time = "2025-10-14T04:41:13.346Z" }, + { url = "https://files.pythonhosted.org/packages/7d/62/73a6d7450829655a35bb88a88fca7d736f9882a27eacdca2c6d505b57e2e/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b39f987ae8ccdf0d2642338faf2abb1862340facc796048b604ef14919e55ed", size = 147936, upload-time = "2025-10-14T04:41:14.461Z" }, + { url = "https://files.pythonhosted.org/packages/89/c5/adb8c8b3d6625bef6d88b251bbb0d95f8205831b987631ab0c8bb5d937c2/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3162d5d8ce1bb98dd51af660f2121c55d0fa541b46dff7bb9b9f86ea1d87de72", size = 144180, upload-time = "2025-10-14T04:41:15.588Z" }, + { url = "https://files.pythonhosted.org/packages/91/ed/9706e4070682d1cc219050b6048bfd293ccf67b3d4f5a4f39207453d4b99/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:81d5eb2a312700f4ecaa977a8235b634ce853200e828fbadf3a9c50bab278328", size = 161346, upload-time = "2025-10-14T04:41:16.738Z" }, + { url = "https://files.pythonhosted.org/packages/d5/0d/031f0d95e4972901a2f6f09ef055751805ff541511dc1252ba3ca1f80cf5/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5bd2293095d766545ec1a8f612559f6b40abc0eb18bb2f5d1171872d34036ede", size = 158874, upload-time = "2025-10-14T04:41:17.923Z" }, + { url = "https://files.pythonhosted.org/packages/f5/83/6ab5883f57c9c801ce5e5677242328aa45592be8a00644310a008d04f922/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a8a8b89589086a25749f471e6a900d3f662d1d3b6e2e59dcecf787b1cc3a1894", size = 153076, upload-time = "2025-10-14T04:41:19.106Z" }, + { url = "https://files.pythonhosted.org/packages/75/1e/5ff781ddf5260e387d6419959ee89ef13878229732732ee73cdae01800f2/charset_normalizer-3.4.4-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bc7637e2f80d8530ee4a78e878bce464f70087ce73cf7c1caf142416923b98f1", size = 150601, upload-time = "2025-10-14T04:41:20.245Z" }, + { url = "https://files.pythonhosted.org/packages/d7/57/71be810965493d3510a6ca79b90c19e48696fb1ff964da319334b12677f0/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f8bf04158c6b607d747e93949aa60618b61312fe647a6369f88ce2ff16043490", size = 150376, upload-time = "2025-10-14T04:41:21.398Z" }, + { url = "https://files.pythonhosted.org/packages/e5/d5/c3d057a78c181d007014feb7e9f2e65905a6c4ef182c0ddf0de2924edd65/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:554af85e960429cf30784dd47447d5125aaa3b99a6f0683589dbd27e2f45da44", size = 144825, upload-time = "2025-10-14T04:41:22.583Z" }, + { url = "https://files.pythonhosted.org/packages/e6/8c/d0406294828d4976f275ffbe66f00266c4b3136b7506941d87c00cab5272/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:74018750915ee7ad843a774364e13a3db91682f26142baddf775342c3f5b1133", size = 162583, upload-time = "2025-10-14T04:41:23.754Z" }, + { url = "https://files.pythonhosted.org/packages/d7/24/e2aa1f18c8f15c4c0e932d9287b8609dd30ad56dbe41d926bd846e22fb8d/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:c0463276121fdee9c49b98908b3a89c39be45d86d1dbaa22957e38f6321d4ce3", size = 150366, upload-time = "2025-10-14T04:41:25.27Z" }, + { url = "https://files.pythonhosted.org/packages/e4/5b/1e6160c7739aad1e2df054300cc618b06bf784a7a164b0f238360721ab86/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:362d61fd13843997c1c446760ef36f240cf81d3ebf74ac62652aebaf7838561e", size = 160300, upload-time = "2025-10-14T04:41:26.725Z" }, + { url = "https://files.pythonhosted.org/packages/7a/10/f882167cd207fbdd743e55534d5d9620e095089d176d55cb22d5322f2afd/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9a26f18905b8dd5d685d6d07b0cdf98a79f3c7a918906af7cc143ea2e164c8bc", size = 154465, upload-time = "2025-10-14T04:41:28.322Z" }, + { url = "https://files.pythonhosted.org/packages/89/66/c7a9e1b7429be72123441bfdbaf2bc13faab3f90b933f664db506dea5915/charset_normalizer-3.4.4-cp313-cp313-win32.whl", hash = "sha256:9b35f4c90079ff2e2edc5b26c0c77925e5d2d255c42c74fdb70fb49b172726ac", size = 99404, upload-time = "2025-10-14T04:41:29.95Z" }, + { url = "https://files.pythonhosted.org/packages/c4/26/b9924fa27db384bdcd97ab83b4f0a8058d96ad9626ead570674d5e737d90/charset_normalizer-3.4.4-cp313-cp313-win_amd64.whl", hash = "sha256:b435cba5f4f750aa6c0a0d92c541fb79f69a387c91e61f1795227e4ed9cece14", size = 107092, upload-time = "2025-10-14T04:41:31.188Z" }, + { url = "https://files.pythonhosted.org/packages/af/8f/3ed4bfa0c0c72a7ca17f0380cd9e4dd842b09f664e780c13cff1dcf2ef1b/charset_normalizer-3.4.4-cp313-cp313-win_arm64.whl", hash = "sha256:542d2cee80be6f80247095cc36c418f7bddd14f4a6de45af91dfad36d817bba2", size = 100408, upload-time = "2025-10-14T04:41:32.624Z" }, + { url = "https://files.pythonhosted.org/packages/2a/35/7051599bd493e62411d6ede36fd5af83a38f37c4767b92884df7301db25d/charset_normalizer-3.4.4-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:da3326d9e65ef63a817ecbcc0df6e94463713b754fe293eaa03da99befb9a5bd", size = 207746, upload-time = "2025-10-14T04:41:33.773Z" }, + { url = "https://files.pythonhosted.org/packages/10/9a/97c8d48ef10d6cd4fcead2415523221624bf58bcf68a802721a6bc807c8f/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8af65f14dc14a79b924524b1e7fffe304517b2bff5a58bf64f30b98bbc5079eb", size = 147889, upload-time = "2025-10-14T04:41:34.897Z" }, + { url = "https://files.pythonhosted.org/packages/10/bf/979224a919a1b606c82bd2c5fa49b5c6d5727aa47b4312bb27b1734f53cd/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:74664978bb272435107de04e36db5a9735e78232b85b77d45cfb38f758efd33e", size = 143641, upload-time = "2025-10-14T04:41:36.116Z" }, + { url = "https://files.pythonhosted.org/packages/ba/33/0ad65587441fc730dc7bd90e9716b30b4702dc7b617e6ba4997dc8651495/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:752944c7ffbfdd10c074dc58ec2d5a8a4cd9493b314d367c14d24c17684ddd14", size = 160779, upload-time = "2025-10-14T04:41:37.229Z" }, + { url = "https://files.pythonhosted.org/packages/67/ed/331d6b249259ee71ddea93f6f2f0a56cfebd46938bde6fcc6f7b9a3d0e09/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d1f13550535ad8cff21b8d757a3257963e951d96e20ec82ab44bc64aeb62a191", size = 159035, upload-time = "2025-10-14T04:41:38.368Z" }, + { url = "https://files.pythonhosted.org/packages/67/ff/f6b948ca32e4f2a4576aa129d8bed61f2e0543bf9f5f2b7fc3758ed005c9/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ecaae4149d99b1c9e7b88bb03e3221956f68fd6d50be2ef061b2381b61d20838", size = 152542, upload-time = "2025-10-14T04:41:39.862Z" }, + { url = "https://files.pythonhosted.org/packages/16/85/276033dcbcc369eb176594de22728541a925b2632f9716428c851b149e83/charset_normalizer-3.4.4-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:cb6254dc36b47a990e59e1068afacdcd02958bdcce30bb50cc1700a8b9d624a6", size = 149524, upload-time = "2025-10-14T04:41:41.319Z" }, + { url = "https://files.pythonhosted.org/packages/9e/f2/6a2a1f722b6aba37050e626530a46a68f74e63683947a8acff92569f979a/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c8ae8a0f02f57a6e61203a31428fa1d677cbe50c93622b4149d5c0f319c1d19e", size = 150395, upload-time = "2025-10-14T04:41:42.539Z" }, + { url = "https://files.pythonhosted.org/packages/60/bb/2186cb2f2bbaea6338cad15ce23a67f9b0672929744381e28b0592676824/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:47cc91b2f4dd2833fddaedd2893006b0106129d4b94fdb6af1f4ce5a9965577c", size = 143680, upload-time = "2025-10-14T04:41:43.661Z" }, + { url = "https://files.pythonhosted.org/packages/7d/a5/bf6f13b772fbb2a90360eb620d52ed8f796f3c5caee8398c3b2eb7b1c60d/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:82004af6c302b5d3ab2cfc4cc5f29db16123b1a8417f2e25f9066f91d4411090", size = 162045, upload-time = "2025-10-14T04:41:44.821Z" }, + { url = "https://files.pythonhosted.org/packages/df/c5/d1be898bf0dc3ef9030c3825e5d3b83f2c528d207d246cbabe245966808d/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:2b7d8f6c26245217bd2ad053761201e9f9680f8ce52f0fcd8d0755aeae5b2152", size = 149687, upload-time = "2025-10-14T04:41:46.442Z" }, + { url = "https://files.pythonhosted.org/packages/a5/42/90c1f7b9341eef50c8a1cb3f098ac43b0508413f33affd762855f67a410e/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:799a7a5e4fb2d5898c60b640fd4981d6a25f1c11790935a44ce38c54e985f828", size = 160014, upload-time = "2025-10-14T04:41:47.631Z" }, + { url = "https://files.pythonhosted.org/packages/76/be/4d3ee471e8145d12795ab655ece37baed0929462a86e72372fd25859047c/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:99ae2cffebb06e6c22bdc25801d7b30f503cc87dbd283479e7b606f70aff57ec", size = 154044, upload-time = "2025-10-14T04:41:48.81Z" }, + { url = "https://files.pythonhosted.org/packages/b0/6f/8f7af07237c34a1defe7defc565a9bc1807762f672c0fde711a4b22bf9c0/charset_normalizer-3.4.4-cp314-cp314-win32.whl", hash = "sha256:f9d332f8c2a2fcbffe1378594431458ddbef721c1769d78e2cbc06280d8155f9", size = 99940, upload-time = "2025-10-14T04:41:49.946Z" }, + { url = "https://files.pythonhosted.org/packages/4b/51/8ade005e5ca5b0d80fb4aff72a3775b325bdc3d27408c8113811a7cbe640/charset_normalizer-3.4.4-cp314-cp314-win_amd64.whl", hash = "sha256:8a6562c3700cce886c5be75ade4a5db4214fda19fede41d9792d100288d8f94c", size = 107104, upload-time = "2025-10-14T04:41:51.051Z" }, + { url = "https://files.pythonhosted.org/packages/da/5f/6b8f83a55bb8278772c5ae54a577f3099025f9ade59d0136ac24a0df4bde/charset_normalizer-3.4.4-cp314-cp314-win_arm64.whl", hash = "sha256:de00632ca48df9daf77a2c65a484531649261ec9f25489917f09e455cb09ddb2", size = 100743, upload-time = "2025-10-14T04:41:52.122Z" }, + { url = "https://files.pythonhosted.org/packages/0a/4c/925909008ed5a988ccbb72dcc897407e5d6d3bd72410d69e051fc0c14647/charset_normalizer-3.4.4-py3-none-any.whl", hash = "sha256:7a32c560861a02ff789ad905a2fe94e3f840803362c84fecf1851cb4cf3dc37f", size = 53402, upload-time = "2025-10-14T04:42:31.76Z" }, +] + [[package]] name = "click" version = "8.1.8" @@ -188,6 +336,79 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/65/a4/d2f7be3c86708912c02571db0b550121caab8cd88a3c0aacb9cfa15ea66e/fonttools-4.59.2-py3-none-any.whl", hash = "sha256:8bd0f759020e87bb5d323e6283914d9bf4ae35a7307dafb2cbd1e379e720ad37", size = 1132315, upload-time = "2025-08-27T16:40:28.984Z" }, ] +[[package]] +name = "frozenlist" +version = "1.8.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/2d/f5/c831fac6cc817d26fd54c7eaccd04ef7e0288806943f7cc5bbf69f3ac1f0/frozenlist-1.8.0.tar.gz", hash = "sha256:3ede829ed8d842f6cd48fc7081d7a41001a56f1f38603f9d49bf3020d59a31ad", size = 45875, upload-time = "2025-10-06T05:38:17.865Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2d/40/0832c31a37d60f60ed79e9dfb5a92e1e2af4f40a16a29abcc7992af9edff/frozenlist-1.8.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:8d92f1a84bb12d9e56f818b3a746f3efba93c1b63c8387a73dde655e1e42282a", size = 85717, upload-time = "2025-10-06T05:36:27.341Z" }, + { url = "https://files.pythonhosted.org/packages/30/ba/b0b3de23f40bc55a7057bd38434e25c34fa48e17f20ee273bbde5e0650f3/frozenlist-1.8.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:96153e77a591c8adc2ee805756c61f59fef4cf4073a9275ee86fe8cba41241f7", size = 49651, upload-time = "2025-10-06T05:36:28.855Z" }, + { url = "https://files.pythonhosted.org/packages/0c/ab/6e5080ee374f875296c4243c381bbdef97a9ac39c6e3ce1d5f7d42cb78d6/frozenlist-1.8.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f21f00a91358803399890ab167098c131ec2ddd5f8f5fd5fe9c9f2c6fcd91e40", size = 49417, upload-time = "2025-10-06T05:36:29.877Z" }, + { url = "https://files.pythonhosted.org/packages/d5/4e/e4691508f9477ce67da2015d8c00acd751e6287739123113a9fca6f1604e/frozenlist-1.8.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:fb30f9626572a76dfe4293c7194a09fb1fe93ba94c7d4f720dfae3b646b45027", size = 234391, upload-time = "2025-10-06T05:36:31.301Z" }, + { url = "https://files.pythonhosted.org/packages/40/76/c202df58e3acdf12969a7895fd6f3bc016c642e6726aa63bd3025e0fc71c/frozenlist-1.8.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:eaa352d7047a31d87dafcacbabe89df0aa506abb5b1b85a2fb91bc3faa02d822", size = 233048, upload-time = "2025-10-06T05:36:32.531Z" }, + { url = "https://files.pythonhosted.org/packages/f9/c0/8746afb90f17b73ca5979c7a3958116e105ff796e718575175319b5bb4ce/frozenlist-1.8.0-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:03ae967b4e297f58f8c774c7eabcce57fe3c2434817d4385c50661845a058121", size = 226549, upload-time = "2025-10-06T05:36:33.706Z" }, + { url = "https://files.pythonhosted.org/packages/7e/eb/4c7eefc718ff72f9b6c4893291abaae5fbc0c82226a32dcd8ef4f7a5dbef/frozenlist-1.8.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f6292f1de555ffcc675941d65fffffb0a5bcd992905015f85d0592201793e0e5", size = 239833, upload-time = "2025-10-06T05:36:34.947Z" }, + { url = "https://files.pythonhosted.org/packages/c2/4e/e5c02187cf704224f8b21bee886f3d713ca379535f16893233b9d672ea71/frozenlist-1.8.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:29548f9b5b5e3460ce7378144c3010363d8035cea44bc0bf02d57f5a685e084e", size = 245363, upload-time = "2025-10-06T05:36:36.534Z" }, + { url = "https://files.pythonhosted.org/packages/1f/96/cb85ec608464472e82ad37a17f844889c36100eed57bea094518bf270692/frozenlist-1.8.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ec3cc8c5d4084591b4237c0a272cc4f50a5b03396a47d9caaf76f5d7b38a4f11", size = 229314, upload-time = "2025-10-06T05:36:38.582Z" }, + { url = "https://files.pythonhosted.org/packages/5d/6f/4ae69c550e4cee66b57887daeebe006fe985917c01d0fff9caab9883f6d0/frozenlist-1.8.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:517279f58009d0b1f2e7c1b130b377a349405da3f7621ed6bfae50b10adf20c1", size = 243365, upload-time = "2025-10-06T05:36:40.152Z" }, + { url = "https://files.pythonhosted.org/packages/7a/58/afd56de246cf11780a40a2c28dc7cbabbf06337cc8ddb1c780a2d97e88d8/frozenlist-1.8.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:db1e72ede2d0d7ccb213f218df6a078a9c09a7de257c2fe8fcef16d5925230b1", size = 237763, upload-time = "2025-10-06T05:36:41.355Z" }, + { url = "https://files.pythonhosted.org/packages/cb/36/cdfaf6ed42e2644740d4a10452d8e97fa1c062e2a8006e4b09f1b5fd7d63/frozenlist-1.8.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:b4dec9482a65c54a5044486847b8a66bf10c9cb4926d42927ec4e8fd5db7fed8", size = 240110, upload-time = "2025-10-06T05:36:42.716Z" }, + { url = "https://files.pythonhosted.org/packages/03/a8/9ea226fbefad669f11b52e864c55f0bd57d3c8d7eb07e9f2e9a0b39502e1/frozenlist-1.8.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:21900c48ae04d13d416f0e1e0c4d81f7931f73a9dfa0b7a8746fb2fe7dd970ed", size = 233717, upload-time = "2025-10-06T05:36:44.251Z" }, + { url = "https://files.pythonhosted.org/packages/1e/0b/1b5531611e83ba7d13ccc9988967ea1b51186af64c42b7a7af465dcc9568/frozenlist-1.8.0-cp313-cp313-win32.whl", hash = "sha256:8b7b94a067d1c504ee0b16def57ad5738701e4ba10cec90529f13fa03c833496", size = 39628, upload-time = "2025-10-06T05:36:45.423Z" }, + { url = "https://files.pythonhosted.org/packages/d8/cf/174c91dbc9cc49bc7b7aab74d8b734e974d1faa8f191c74af9b7e80848e6/frozenlist-1.8.0-cp313-cp313-win_amd64.whl", hash = "sha256:878be833caa6a3821caf85eb39c5ba92d28e85df26d57afb06b35b2efd937231", size = 43882, upload-time = "2025-10-06T05:36:46.796Z" }, + { url = "https://files.pythonhosted.org/packages/c1/17/502cd212cbfa96eb1388614fe39a3fc9ab87dbbe042b66f97acb57474834/frozenlist-1.8.0-cp313-cp313-win_arm64.whl", hash = "sha256:44389d135b3ff43ba8cc89ff7f51f5a0bb6b63d829c8300f79a2fe4fe61bcc62", size = 39676, upload-time = "2025-10-06T05:36:47.8Z" }, + { url = "https://files.pythonhosted.org/packages/d2/5c/3bbfaa920dfab09e76946a5d2833a7cbdf7b9b4a91c714666ac4855b88b4/frozenlist-1.8.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:e25ac20a2ef37e91c1b39938b591457666a0fa835c7783c3a8f33ea42870db94", size = 89235, upload-time = "2025-10-06T05:36:48.78Z" }, + { url = "https://files.pythonhosted.org/packages/d2/d6/f03961ef72166cec1687e84e8925838442b615bd0b8854b54923ce5b7b8a/frozenlist-1.8.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:07cdca25a91a4386d2e76ad992916a85038a9b97561bf7a3fd12d5d9ce31870c", size = 50742, upload-time = "2025-10-06T05:36:49.837Z" }, + { url = "https://files.pythonhosted.org/packages/1e/bb/a6d12b7ba4c3337667d0e421f7181c82dda448ce4e7ad7ecd249a16fa806/frozenlist-1.8.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:4e0c11f2cc6717e0a741f84a527c52616140741cd812a50422f83dc31749fb52", size = 51725, upload-time = "2025-10-06T05:36:50.851Z" }, + { url = "https://files.pythonhosted.org/packages/bc/71/d1fed0ffe2c2ccd70b43714c6cab0f4188f09f8a67a7914a6b46ee30f274/frozenlist-1.8.0-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:b3210649ee28062ea6099cfda39e147fa1bc039583c8ee4481cb7811e2448c51", size = 284533, upload-time = "2025-10-06T05:36:51.898Z" }, + { url = "https://files.pythonhosted.org/packages/c9/1f/fb1685a7b009d89f9bf78a42d94461bc06581f6e718c39344754a5d9bada/frozenlist-1.8.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:581ef5194c48035a7de2aefc72ac6539823bb71508189e5de01d60c9dcd5fa65", size = 292506, upload-time = "2025-10-06T05:36:53.101Z" }, + { url = "https://files.pythonhosted.org/packages/e6/3b/b991fe1612703f7e0d05c0cf734c1b77aaf7c7d321df4572e8d36e7048c8/frozenlist-1.8.0-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3ef2d026f16a2b1866e1d86fc4e1291e1ed8a387b2c333809419a2f8b3a77b82", size = 274161, upload-time = "2025-10-06T05:36:54.309Z" }, + { url = "https://files.pythonhosted.org/packages/ca/ec/c5c618767bcdf66e88945ec0157d7f6c4a1322f1473392319b7a2501ded7/frozenlist-1.8.0-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:5500ef82073f599ac84d888e3a8c1f77ac831183244bfd7f11eaa0289fb30714", size = 294676, upload-time = "2025-10-06T05:36:55.566Z" }, + { url = "https://files.pythonhosted.org/packages/7c/ce/3934758637d8f8a88d11f0585d6495ef54b2044ed6ec84492a91fa3b27aa/frozenlist-1.8.0-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:50066c3997d0091c411a66e710f4e11752251e6d2d73d70d8d5d4c76442a199d", size = 300638, upload-time = "2025-10-06T05:36:56.758Z" }, + { url = "https://files.pythonhosted.org/packages/fc/4f/a7e4d0d467298f42de4b41cbc7ddaf19d3cfeabaf9ff97c20c6c7ee409f9/frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:5c1c8e78426e59b3f8005e9b19f6ff46e5845895adbde20ece9218319eca6506", size = 283067, upload-time = "2025-10-06T05:36:57.965Z" }, + { url = "https://files.pythonhosted.org/packages/dc/48/c7b163063d55a83772b268e6d1affb960771b0e203b632cfe09522d67ea5/frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:eefdba20de0d938cec6a89bd4d70f346a03108a19b9df4248d3cf0d88f1b0f51", size = 292101, upload-time = "2025-10-06T05:36:59.237Z" }, + { url = "https://files.pythonhosted.org/packages/9f/d0/2366d3c4ecdc2fd391e0afa6e11500bfba0ea772764d631bbf82f0136c9d/frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:cf253e0e1c3ceb4aaff6df637ce033ff6535fb8c70a764a8f46aafd3d6ab798e", size = 289901, upload-time = "2025-10-06T05:37:00.811Z" }, + { url = "https://files.pythonhosted.org/packages/b8/94/daff920e82c1b70e3618a2ac39fbc01ae3e2ff6124e80739ce5d71c9b920/frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:032efa2674356903cd0261c4317a561a6850f3ac864a63fc1583147fb05a79b0", size = 289395, upload-time = "2025-10-06T05:37:02.115Z" }, + { url = "https://files.pythonhosted.org/packages/e3/20/bba307ab4235a09fdcd3cc5508dbabd17c4634a1af4b96e0f69bfe551ebd/frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:6da155091429aeba16851ecb10a9104a108bcd32f6c1642867eadaee401c1c41", size = 283659, upload-time = "2025-10-06T05:37:03.711Z" }, + { url = "https://files.pythonhosted.org/packages/fd/00/04ca1c3a7a124b6de4f8a9a17cc2fcad138b4608e7a3fc5877804b8715d7/frozenlist-1.8.0-cp313-cp313t-win32.whl", hash = "sha256:0f96534f8bfebc1a394209427d0f8a63d343c9779cda6fc25e8e121b5fd8555b", size = 43492, upload-time = "2025-10-06T05:37:04.915Z" }, + { url = "https://files.pythonhosted.org/packages/59/5e/c69f733a86a94ab10f68e496dc6b7e8bc078ebb415281d5698313e3af3a1/frozenlist-1.8.0-cp313-cp313t-win_amd64.whl", hash = "sha256:5d63a068f978fc69421fb0e6eb91a9603187527c86b7cd3f534a5b77a592b888", size = 48034, upload-time = "2025-10-06T05:37:06.343Z" }, + { url = "https://files.pythonhosted.org/packages/16/6c/be9d79775d8abe79b05fa6d23da99ad6e7763a1d080fbae7290b286093fd/frozenlist-1.8.0-cp313-cp313t-win_arm64.whl", hash = "sha256:bf0a7e10b077bf5fb9380ad3ae8ce20ef919a6ad93b4552896419ac7e1d8e042", size = 41749, upload-time = "2025-10-06T05:37:07.431Z" }, + { url = "https://files.pythonhosted.org/packages/f1/c8/85da824b7e7b9b6e7f7705b2ecaf9591ba6f79c1177f324c2735e41d36a2/frozenlist-1.8.0-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:cee686f1f4cadeb2136007ddedd0aaf928ab95216e7691c63e50a8ec066336d0", size = 86127, upload-time = "2025-10-06T05:37:08.438Z" }, + { url = "https://files.pythonhosted.org/packages/8e/e8/a1185e236ec66c20afd72399522f142c3724c785789255202d27ae992818/frozenlist-1.8.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:119fb2a1bd47307e899c2fac7f28e85b9a543864df47aa7ec9d3c1b4545f096f", size = 49698, upload-time = "2025-10-06T05:37:09.48Z" }, + { url = "https://files.pythonhosted.org/packages/a1/93/72b1736d68f03fda5fdf0f2180fb6caaae3894f1b854d006ac61ecc727ee/frozenlist-1.8.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:4970ece02dbc8c3a92fcc5228e36a3e933a01a999f7094ff7c23fbd2beeaa67c", size = 49749, upload-time = "2025-10-06T05:37:10.569Z" }, + { url = "https://files.pythonhosted.org/packages/a7/b2/fabede9fafd976b991e9f1b9c8c873ed86f202889b864756f240ce6dd855/frozenlist-1.8.0-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:cba69cb73723c3f329622e34bdbf5ce1f80c21c290ff04256cff1cd3c2036ed2", size = 231298, upload-time = "2025-10-06T05:37:11.993Z" }, + { url = "https://files.pythonhosted.org/packages/3a/3b/d9b1e0b0eed36e70477ffb8360c49c85c8ca8ef9700a4e6711f39a6e8b45/frozenlist-1.8.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:778a11b15673f6f1df23d9586f83c4846c471a8af693a22e066508b77d201ec8", size = 232015, upload-time = "2025-10-06T05:37:13.194Z" }, + { url = "https://files.pythonhosted.org/packages/dc/94/be719d2766c1138148564a3960fc2c06eb688da592bdc25adcf856101be7/frozenlist-1.8.0-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:0325024fe97f94c41c08872db482cf8ac4800d80e79222c6b0b7b162d5b13686", size = 225038, upload-time = "2025-10-06T05:37:14.577Z" }, + { url = "https://files.pythonhosted.org/packages/e4/09/6712b6c5465f083f52f50cf74167b92d4ea2f50e46a9eea0523d658454ae/frozenlist-1.8.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:97260ff46b207a82a7567b581ab4190bd4dfa09f4db8a8b49d1a958f6aa4940e", size = 240130, upload-time = "2025-10-06T05:37:15.781Z" }, + { url = "https://files.pythonhosted.org/packages/f8/d4/cd065cdcf21550b54f3ce6a22e143ac9e4836ca42a0de1022da8498eac89/frozenlist-1.8.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:54b2077180eb7f83dd52c40b2750d0a9f175e06a42e3213ce047219de902717a", size = 242845, upload-time = "2025-10-06T05:37:17.037Z" }, + { url = "https://files.pythonhosted.org/packages/62/c3/f57a5c8c70cd1ead3d5d5f776f89d33110b1addae0ab010ad774d9a44fb9/frozenlist-1.8.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:2f05983daecab868a31e1da44462873306d3cbfd76d1f0b5b69c473d21dbb128", size = 229131, upload-time = "2025-10-06T05:37:18.221Z" }, + { url = "https://files.pythonhosted.org/packages/6c/52/232476fe9cb64f0742f3fde2b7d26c1dac18b6d62071c74d4ded55e0ef94/frozenlist-1.8.0-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:33f48f51a446114bc5d251fb2954ab0164d5be02ad3382abcbfe07e2531d650f", size = 240542, upload-time = "2025-10-06T05:37:19.771Z" }, + { url = "https://files.pythonhosted.org/packages/5f/85/07bf3f5d0fb5414aee5f47d33c6f5c77bfe49aac680bfece33d4fdf6a246/frozenlist-1.8.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:154e55ec0655291b5dd1b8731c637ecdb50975a2ae70c606d100750a540082f7", size = 237308, upload-time = "2025-10-06T05:37:20.969Z" }, + { url = "https://files.pythonhosted.org/packages/11/99/ae3a33d5befd41ac0ca2cc7fd3aa707c9c324de2e89db0e0f45db9a64c26/frozenlist-1.8.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:4314debad13beb564b708b4a496020e5306c7333fa9a3ab90374169a20ffab30", size = 238210, upload-time = "2025-10-06T05:37:22.252Z" }, + { url = "https://files.pythonhosted.org/packages/b2/60/b1d2da22f4970e7a155f0adde9b1435712ece01b3cd45ba63702aea33938/frozenlist-1.8.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:073f8bf8becba60aa931eb3bc420b217bb7d5b8f4750e6f8b3be7f3da85d38b7", size = 231972, upload-time = "2025-10-06T05:37:23.5Z" }, + { url = "https://files.pythonhosted.org/packages/3f/ab/945b2f32de889993b9c9133216c068b7fcf257d8595a0ac420ac8677cab0/frozenlist-1.8.0-cp314-cp314-win32.whl", hash = "sha256:bac9c42ba2ac65ddc115d930c78d24ab8d4f465fd3fc473cdedfccadb9429806", size = 40536, upload-time = "2025-10-06T05:37:25.581Z" }, + { url = "https://files.pythonhosted.org/packages/59/ad/9caa9b9c836d9ad6f067157a531ac48b7d36499f5036d4141ce78c230b1b/frozenlist-1.8.0-cp314-cp314-win_amd64.whl", hash = "sha256:3e0761f4d1a44f1d1a47996511752cf3dcec5bbdd9cc2b4fe595caf97754b7a0", size = 44330, upload-time = "2025-10-06T05:37:26.928Z" }, + { url = "https://files.pythonhosted.org/packages/82/13/e6950121764f2676f43534c555249f57030150260aee9dcf7d64efda11dd/frozenlist-1.8.0-cp314-cp314-win_arm64.whl", hash = "sha256:d1eaff1d00c7751b7c6662e9c5ba6eb2c17a2306ba5e2a37f24ddf3cc953402b", size = 40627, upload-time = "2025-10-06T05:37:28.075Z" }, + { url = "https://files.pythonhosted.org/packages/c0/c7/43200656ecc4e02d3f8bc248df68256cd9572b3f0017f0a0c4e93440ae23/frozenlist-1.8.0-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:d3bb933317c52d7ea5004a1c442eef86f426886fba134ef8cf4226ea6ee1821d", size = 89238, upload-time = "2025-10-06T05:37:29.373Z" }, + { url = "https://files.pythonhosted.org/packages/d1/29/55c5f0689b9c0fb765055629f472c0de484dcaf0acee2f7707266ae3583c/frozenlist-1.8.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:8009897cdef112072f93a0efdce29cd819e717fd2f649ee3016efd3cd885a7ed", size = 50738, upload-time = "2025-10-06T05:37:30.792Z" }, + { url = "https://files.pythonhosted.org/packages/ba/7d/b7282a445956506fa11da8c2db7d276adcbf2b17d8bb8407a47685263f90/frozenlist-1.8.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:2c5dcbbc55383e5883246d11fd179782a9d07a986c40f49abe89ddf865913930", size = 51739, upload-time = "2025-10-06T05:37:32.127Z" }, + { url = "https://files.pythonhosted.org/packages/62/1c/3d8622e60d0b767a5510d1d3cf21065b9db874696a51ea6d7a43180a259c/frozenlist-1.8.0-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:39ecbc32f1390387d2aa4f5a995e465e9e2f79ba3adcac92d68e3e0afae6657c", size = 284186, upload-time = "2025-10-06T05:37:33.21Z" }, + { url = "https://files.pythonhosted.org/packages/2d/14/aa36d5f85a89679a85a1d44cd7a6657e0b1c75f61e7cad987b203d2daca8/frozenlist-1.8.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:92db2bf818d5cc8d9c1f1fc56b897662e24ea5adb36ad1f1d82875bd64e03c24", size = 292196, upload-time = "2025-10-06T05:37:36.107Z" }, + { url = "https://files.pythonhosted.org/packages/05/23/6bde59eb55abd407d34f77d39a5126fb7b4f109a3f611d3929f14b700c66/frozenlist-1.8.0-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:2dc43a022e555de94c3b68a4ef0b11c4f747d12c024a520c7101709a2144fb37", size = 273830, upload-time = "2025-10-06T05:37:37.663Z" }, + { url = "https://files.pythonhosted.org/packages/d2/3f/22cff331bfad7a8afa616289000ba793347fcd7bc275f3b28ecea2a27909/frozenlist-1.8.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:cb89a7f2de3602cfed448095bab3f178399646ab7c61454315089787df07733a", size = 294289, upload-time = "2025-10-06T05:37:39.261Z" }, + { url = "https://files.pythonhosted.org/packages/a4/89/5b057c799de4838b6c69aa82b79705f2027615e01be996d2486a69ca99c4/frozenlist-1.8.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:33139dc858c580ea50e7e60a1b0ea003efa1fd42e6ec7fdbad78fff65fad2fd2", size = 300318, upload-time = "2025-10-06T05:37:43.213Z" }, + { url = "https://files.pythonhosted.org/packages/30/de/2c22ab3eb2a8af6d69dc799e48455813bab3690c760de58e1bf43b36da3e/frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:168c0969a329b416119507ba30b9ea13688fafffac1b7822802537569a1cb0ef", size = 282814, upload-time = "2025-10-06T05:37:45.337Z" }, + { url = "https://files.pythonhosted.org/packages/59/f7/970141a6a8dbd7f556d94977858cfb36fa9b66e0892c6dd780d2219d8cd8/frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:28bd570e8e189d7f7b001966435f9dac6718324b5be2990ac496cf1ea9ddb7fe", size = 291762, upload-time = "2025-10-06T05:37:46.657Z" }, + { url = "https://files.pythonhosted.org/packages/c1/15/ca1adae83a719f82df9116d66f5bb28bb95557b3951903d39135620ef157/frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:b2a095d45c5d46e5e79ba1e5b9cb787f541a8dee0433836cea4b96a2c439dcd8", size = 289470, upload-time = "2025-10-06T05:37:47.946Z" }, + { url = "https://files.pythonhosted.org/packages/ac/83/dca6dc53bf657d371fbc88ddeb21b79891e747189c5de990b9dfff2ccba1/frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:eab8145831a0d56ec9c4139b6c3e594c7a83c2c8be25d5bcf2d86136a532287a", size = 289042, upload-time = "2025-10-06T05:37:49.499Z" }, + { url = "https://files.pythonhosted.org/packages/96/52/abddd34ca99be142f354398700536c5bd315880ed0a213812bc491cff5e4/frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:974b28cf63cc99dfb2188d8d222bc6843656188164848c4f679e63dae4b0708e", size = 283148, upload-time = "2025-10-06T05:37:50.745Z" }, + { url = "https://files.pythonhosted.org/packages/af/d3/76bd4ed4317e7119c2b7f57c3f6934aba26d277acc6309f873341640e21f/frozenlist-1.8.0-cp314-cp314t-win32.whl", hash = "sha256:342c97bf697ac5480c0a7ec73cd700ecfa5a8a40ac923bd035484616efecc2df", size = 44676, upload-time = "2025-10-06T05:37:52.222Z" }, + { url = "https://files.pythonhosted.org/packages/89/76/c615883b7b521ead2944bb3480398cbb07e12b7b4e4d073d3752eb721558/frozenlist-1.8.0-cp314-cp314t-win_amd64.whl", hash = "sha256:06be8f67f39c8b1dc671f5d83aaefd3358ae5cdcf8314552c57e7ed3e6475bdd", size = 49451, upload-time = "2025-10-06T05:37:53.425Z" }, + { url = "https://files.pythonhosted.org/packages/e0/a3/5982da14e113d07b325230f95060e2169f5311b1017ea8af2a29b374c289/frozenlist-1.8.0-cp314-cp314t-win_arm64.whl", hash = "sha256:102e6314ca4da683dca92e3b1355490fed5f313b768500084fbe6371fddfdb79", size = 42507, upload-time = "2025-10-06T05:37:54.513Z" }, + { url = "https://files.pythonhosted.org/packages/9a/9a/e35b4a917281c0b8419d4207f4334c8e8c5dbf4f3f5f9ada73958d937dcc/frozenlist-1.8.0-py3-none-any.whl", hash = "sha256:0c18a16eab41e82c295618a77502e17b195883241c563b00f0aa5106fc4eaa0d", size = 13409, upload-time = "2025-10-06T05:38:16.721Z" }, +] + [[package]] name = "fsspec" version = "2025.9.0" @@ -197,6 +418,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/47/71/70db47e4f6ce3e5c37a607355f80da8860a33226be640226ac52cb05ef2e/fsspec-2025.9.0-py3-none-any.whl", hash = "sha256:530dc2a2af60a414a832059574df4a6e10cce927f6f4a78209390fe38955cfb7", size = 199289, upload-time = "2025-09-02T19:10:47.708Z" }, ] +[[package]] +name = "idna" +version = "3.11" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6f/6d/0703ccc57f3a7233505399edb88de3cbd678da106337b9fcde432b65ed60/idna-3.11.tar.gz", hash = "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902", size = 194582, upload-time = "2025-10-12T14:55:20.501Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea", size = 71008, upload-time = "2025-10-12T14:55:18.883Z" }, +] + [[package]] name = "iniconfig" version = "2.0.0" @@ -361,6 +591,87 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/43/e3/7d92a15f894aa0c9c4b49b8ee9ac9850d6e63b03c9c32c0367a13ae62209/mpmath-1.3.0-py3-none-any.whl", hash = "sha256:a0b2b9fe80bbcd81a6647ff13108738cfb482d481d826cc0e02f5b35e5c88d2c", size = 536198, upload-time = "2023-03-07T16:47:09.197Z" }, ] +[[package]] +name = "multidict" +version = "6.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/80/1e/5492c365f222f907de1039b91f922b93fa4f764c713ee858d235495d8f50/multidict-6.7.0.tar.gz", hash = "sha256:c6e99d9a65ca282e578dfea819cfa9c0a62b2499d8677392e09feaf305e9e6f5", size = 101834, upload-time = "2025-10-06T14:52:30.657Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d2/86/33272a544eeb36d66e4d9a920602d1a2f57d4ebea4ef3cdfe5a912574c95/multidict-6.7.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:bee7c0588aa0076ce77c0ea5d19a68d76ad81fcd9fe8501003b9a24f9d4000f6", size = 76135, upload-time = "2025-10-06T14:49:54.26Z" }, + { url = "https://files.pythonhosted.org/packages/91/1c/eb97db117a1ebe46d457a3d235a7b9d2e6dcab174f42d1b67663dd9e5371/multidict-6.7.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:7ef6b61cad77091056ce0e7ce69814ef72afacb150b7ac6a3e9470def2198159", size = 45117, upload-time = "2025-10-06T14:49:55.82Z" }, + { url = "https://files.pythonhosted.org/packages/f1/d8/6c3442322e41fb1dd4de8bd67bfd11cd72352ac131f6368315617de752f1/multidict-6.7.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:9c0359b1ec12b1d6849c59f9d319610b7f20ef990a6d454ab151aa0e3b9f78ca", size = 43472, upload-time = "2025-10-06T14:49:57.048Z" }, + { url = "https://files.pythonhosted.org/packages/75/3f/e2639e80325af0b6c6febdf8e57cc07043ff15f57fa1ef808f4ccb5ac4cd/multidict-6.7.0-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:cd240939f71c64bd658f186330603aac1a9a81bf6273f523fca63673cb7378a8", size = 249342, upload-time = "2025-10-06T14:49:58.368Z" }, + { url = "https://files.pythonhosted.org/packages/5d/cc/84e0585f805cbeaa9cbdaa95f9a3d6aed745b9d25700623ac89a6ecff400/multidict-6.7.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a60a4d75718a5efa473ebd5ab685786ba0c67b8381f781d1be14da49f1a2dc60", size = 257082, upload-time = "2025-10-06T14:49:59.89Z" }, + { url = "https://files.pythonhosted.org/packages/b0/9c/ac851c107c92289acbbf5cfb485694084690c1b17e555f44952c26ddc5bd/multidict-6.7.0-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:53a42d364f323275126aff81fb67c5ca1b7a04fda0546245730a55c8c5f24bc4", size = 240704, upload-time = "2025-10-06T14:50:01.485Z" }, + { url = "https://files.pythonhosted.org/packages/50/cc/5f93e99427248c09da95b62d64b25748a5f5c98c7c2ab09825a1d6af0e15/multidict-6.7.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:3b29b980d0ddbecb736735ee5bef69bb2ddca56eff603c86f3f29a1128299b4f", size = 266355, upload-time = "2025-10-06T14:50:02.955Z" }, + { url = "https://files.pythonhosted.org/packages/ec/0c/2ec1d883ceb79c6f7f6d7ad90c919c898f5d1c6ea96d322751420211e072/multidict-6.7.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f8a93b1c0ed2d04b97a5e9336fd2d33371b9a6e29ab7dd6503d63407c20ffbaf", size = 267259, upload-time = "2025-10-06T14:50:04.446Z" }, + { url = "https://files.pythonhosted.org/packages/c6/2d/f0b184fa88d6630aa267680bdb8623fb69cb0d024b8c6f0d23f9a0f406d3/multidict-6.7.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9ff96e8815eecacc6645da76c413eb3b3d34cfca256c70b16b286a687d013c32", size = 254903, upload-time = "2025-10-06T14:50:05.98Z" }, + { url = "https://files.pythonhosted.org/packages/06/c9/11ea263ad0df7dfabcad404feb3c0dd40b131bc7f232d5537f2fb1356951/multidict-6.7.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:7516c579652f6a6be0e266aec0acd0db80829ca305c3d771ed898538804c2036", size = 252365, upload-time = "2025-10-06T14:50:07.511Z" }, + { url = "https://files.pythonhosted.org/packages/41/88/d714b86ee2c17d6e09850c70c9d310abac3d808ab49dfa16b43aba9d53fd/multidict-6.7.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:040f393368e63fb0f3330e70c26bfd336656bed925e5cbe17c9da839a6ab13ec", size = 250062, upload-time = "2025-10-06T14:50:09.074Z" }, + { url = "https://files.pythonhosted.org/packages/15/fe/ad407bb9e818c2b31383f6131ca19ea7e35ce93cf1310fce69f12e89de75/multidict-6.7.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:b3bc26a951007b1057a1c543af845f1c7e3e71cc240ed1ace7bf4484aa99196e", size = 249683, upload-time = "2025-10-06T14:50:10.714Z" }, + { url = "https://files.pythonhosted.org/packages/8c/a4/a89abdb0229e533fb925e7c6e5c40201c2873efebc9abaf14046a4536ee6/multidict-6.7.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:7b022717c748dd1992a83e219587aabe45980d88969f01b316e78683e6285f64", size = 261254, upload-time = "2025-10-06T14:50:12.28Z" }, + { url = "https://files.pythonhosted.org/packages/8d/aa/0e2b27bd88b40a4fb8dc53dd74eecac70edaa4c1dd0707eb2164da3675b3/multidict-6.7.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:9600082733859f00d79dee64effc7aef1beb26adb297416a4ad2116fd61374bd", size = 257967, upload-time = "2025-10-06T14:50:14.16Z" }, + { url = "https://files.pythonhosted.org/packages/d0/8e/0c67b7120d5d5f6d874ed85a085f9dc770a7f9d8813e80f44a9fec820bb7/multidict-6.7.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:94218fcec4d72bc61df51c198d098ce2b378e0ccbac41ddbed5ef44092913288", size = 250085, upload-time = "2025-10-06T14:50:15.639Z" }, + { url = "https://files.pythonhosted.org/packages/ba/55/b73e1d624ea4b8fd4dd07a3bb70f6e4c7c6c5d9d640a41c6ffe5cdbd2a55/multidict-6.7.0-cp313-cp313-win32.whl", hash = "sha256:a37bd74c3fa9d00be2d7b8eca074dc56bd8077ddd2917a839bd989612671ed17", size = 41713, upload-time = "2025-10-06T14:50:17.066Z" }, + { url = "https://files.pythonhosted.org/packages/32/31/75c59e7d3b4205075b4c183fa4ca398a2daf2303ddf616b04ae6ef55cffe/multidict-6.7.0-cp313-cp313-win_amd64.whl", hash = "sha256:30d193c6cc6d559db42b6bcec8a5d395d34d60c9877a0b71ecd7c204fcf15390", size = 45915, upload-time = "2025-10-06T14:50:18.264Z" }, + { url = "https://files.pythonhosted.org/packages/31/2a/8987831e811f1184c22bc2e45844934385363ee61c0a2dcfa8f71b87e608/multidict-6.7.0-cp313-cp313-win_arm64.whl", hash = "sha256:ea3334cabe4d41b7ccd01e4d349828678794edbc2d3ae97fc162a3312095092e", size = 43077, upload-time = "2025-10-06T14:50:19.853Z" }, + { url = "https://files.pythonhosted.org/packages/e8/68/7b3a5170a382a340147337b300b9eb25a9ddb573bcdfff19c0fa3f31ffba/multidict-6.7.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:ad9ce259f50abd98a1ca0aa6e490b58c316a0fce0617f609723e40804add2c00", size = 83114, upload-time = "2025-10-06T14:50:21.223Z" }, + { url = "https://files.pythonhosted.org/packages/55/5c/3fa2d07c84df4e302060f555bbf539310980362236ad49f50eeb0a1c1eb9/multidict-6.7.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:07f5594ac6d084cbb5de2df218d78baf55ef150b91f0ff8a21cc7a2e3a5a58eb", size = 48442, upload-time = "2025-10-06T14:50:22.871Z" }, + { url = "https://files.pythonhosted.org/packages/fc/56/67212d33239797f9bd91962bb899d72bb0f4c35a8652dcdb8ed049bef878/multidict-6.7.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:0591b48acf279821a579282444814a2d8d0af624ae0bc600aa4d1b920b6e924b", size = 46885, upload-time = "2025-10-06T14:50:24.258Z" }, + { url = "https://files.pythonhosted.org/packages/46/d1/908f896224290350721597a61a69cd19b89ad8ee0ae1f38b3f5cd12ea2ac/multidict-6.7.0-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:749a72584761531d2b9467cfbdfd29487ee21124c304c4b6cb760d8777b27f9c", size = 242588, upload-time = "2025-10-06T14:50:25.716Z" }, + { url = "https://files.pythonhosted.org/packages/ab/67/8604288bbd68680eee0ab568fdcb56171d8b23a01bcd5cb0c8fedf6e5d99/multidict-6.7.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b4c3d199f953acd5b446bf7c0de1fe25d94e09e79086f8dc2f48a11a129cdf1", size = 249966, upload-time = "2025-10-06T14:50:28.192Z" }, + { url = "https://files.pythonhosted.org/packages/20/33/9228d76339f1ba51e3efef7da3ebd91964d3006217aae13211653193c3ff/multidict-6.7.0-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:9fb0211dfc3b51efea2f349ec92c114d7754dd62c01f81c3e32b765b70c45c9b", size = 228618, upload-time = "2025-10-06T14:50:29.82Z" }, + { url = "https://files.pythonhosted.org/packages/f8/2d/25d9b566d10cab1c42b3b9e5b11ef79c9111eaf4463b8c257a3bd89e0ead/multidict-6.7.0-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a027ec240fe73a8d6281872690b988eed307cd7d91b23998ff35ff577ca688b5", size = 257539, upload-time = "2025-10-06T14:50:31.731Z" }, + { url = "https://files.pythonhosted.org/packages/b6/b1/8d1a965e6637fc33de3c0d8f414485c2b7e4af00f42cab3d84e7b955c222/multidict-6.7.0-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d1d964afecdf3a8288789df2f5751dc0a8261138c3768d9af117ed384e538fad", size = 256345, upload-time = "2025-10-06T14:50:33.26Z" }, + { url = "https://files.pythonhosted.org/packages/ba/0c/06b5a8adbdeedada6f4fb8d8f193d44a347223b11939b42953eeb6530b6b/multidict-6.7.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:caf53b15b1b7df9fbd0709aa01409000a2b4dd03a5f6f5cc548183c7c8f8b63c", size = 247934, upload-time = "2025-10-06T14:50:34.808Z" }, + { url = "https://files.pythonhosted.org/packages/8f/31/b2491b5fe167ca044c6eb4b8f2c9f3b8a00b24c432c365358eadac5d7625/multidict-6.7.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:654030da3197d927f05a536a66186070e98765aa5142794c9904555d3a9d8fb5", size = 245243, upload-time = "2025-10-06T14:50:36.436Z" }, + { url = "https://files.pythonhosted.org/packages/61/1a/982913957cb90406c8c94f53001abd9eafc271cb3e70ff6371590bec478e/multidict-6.7.0-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:2090d3718829d1e484706a2f525e50c892237b2bf9b17a79b059cb98cddc2f10", size = 235878, upload-time = "2025-10-06T14:50:37.953Z" }, + { url = "https://files.pythonhosted.org/packages/be/c0/21435d804c1a1cf7a2608593f4d19bca5bcbd7a81a70b253fdd1c12af9c0/multidict-6.7.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:2d2cfeec3f6f45651b3d408c4acec0ebf3daa9bc8a112a084206f5db5d05b754", size = 243452, upload-time = "2025-10-06T14:50:39.574Z" }, + { url = "https://files.pythonhosted.org/packages/54/0a/4349d540d4a883863191be6eb9a928846d4ec0ea007d3dcd36323bb058ac/multidict-6.7.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:4ef089f985b8c194d341eb2c24ae6e7408c9a0e2e5658699c92f497437d88c3c", size = 252312, upload-time = "2025-10-06T14:50:41.612Z" }, + { url = "https://files.pythonhosted.org/packages/26/64/d5416038dbda1488daf16b676e4dbfd9674dde10a0cc8f4fc2b502d8125d/multidict-6.7.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:e93a0617cd16998784bf4414c7e40f17a35d2350e5c6f0bd900d3a8e02bd3762", size = 246935, upload-time = "2025-10-06T14:50:43.972Z" }, + { url = "https://files.pythonhosted.org/packages/9f/8c/8290c50d14e49f35e0bd4abc25e1bc7711149ca9588ab7d04f886cdf03d9/multidict-6.7.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:f0feece2ef8ebc42ed9e2e8c78fc4aa3cf455733b507c09ef7406364c94376c6", size = 243385, upload-time = "2025-10-06T14:50:45.648Z" }, + { url = "https://files.pythonhosted.org/packages/ef/a0/f83ae75e42d694b3fbad3e047670e511c138be747bc713cf1b10d5096416/multidict-6.7.0-cp313-cp313t-win32.whl", hash = "sha256:19a1d55338ec1be74ef62440ca9e04a2f001a04d0cc49a4983dc320ff0f3212d", size = 47777, upload-time = "2025-10-06T14:50:47.154Z" }, + { url = "https://files.pythonhosted.org/packages/dc/80/9b174a92814a3830b7357307a792300f42c9e94664b01dee8e457551fa66/multidict-6.7.0-cp313-cp313t-win_amd64.whl", hash = "sha256:3da4fb467498df97e986af166b12d01f05d2e04f978a9c1c680ea1988e0bc4b6", size = 53104, upload-time = "2025-10-06T14:50:48.851Z" }, + { url = "https://files.pythonhosted.org/packages/cc/28/04baeaf0428d95bb7a7bea0e691ba2f31394338ba424fb0679a9ed0f4c09/multidict-6.7.0-cp313-cp313t-win_arm64.whl", hash = "sha256:b4121773c49a0776461f4a904cdf6264c88e42218aaa8407e803ca8025872792", size = 45503, upload-time = "2025-10-06T14:50:50.16Z" }, + { url = "https://files.pythonhosted.org/packages/e2/b1/3da6934455dd4b261d4c72f897e3a5728eba81db59959f3a639245891baa/multidict-6.7.0-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:3bab1e4aff7adaa34410f93b1f8e57c4b36b9af0426a76003f441ee1d3c7e842", size = 75128, upload-time = "2025-10-06T14:50:51.92Z" }, + { url = "https://files.pythonhosted.org/packages/14/2c/f069cab5b51d175a1a2cb4ccdf7a2c2dabd58aa5bd933fa036a8d15e2404/multidict-6.7.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:b8512bac933afc3e45fb2b18da8e59b78d4f408399a960339598374d4ae3b56b", size = 44410, upload-time = "2025-10-06T14:50:53.275Z" }, + { url = "https://files.pythonhosted.org/packages/42/e2/64bb41266427af6642b6b128e8774ed84c11b80a90702c13ac0a86bb10cc/multidict-6.7.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:79dcf9e477bc65414ebfea98ffd013cb39552b5ecd62908752e0e413d6d06e38", size = 43205, upload-time = "2025-10-06T14:50:54.911Z" }, + { url = "https://files.pythonhosted.org/packages/02/68/6b086fef8a3f1a8541b9236c594f0c9245617c29841f2e0395d979485cde/multidict-6.7.0-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:31bae522710064b5cbeddaf2e9f32b1abab70ac6ac91d42572502299e9953128", size = 245084, upload-time = "2025-10-06T14:50:56.369Z" }, + { url = "https://files.pythonhosted.org/packages/15/ee/f524093232007cd7a75c1d132df70f235cfd590a7c9eaccd7ff422ef4ae8/multidict-6.7.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4a0df7ff02397bb63e2fd22af2c87dfa39e8c7f12947bc524dbdc528282c7e34", size = 252667, upload-time = "2025-10-06T14:50:57.991Z" }, + { url = "https://files.pythonhosted.org/packages/02/a5/eeb3f43ab45878f1895118c3ef157a480db58ede3f248e29b5354139c2c9/multidict-6.7.0-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:7a0222514e8e4c514660e182d5156a415c13ef0aabbd71682fc714e327b95e99", size = 233590, upload-time = "2025-10-06T14:50:59.589Z" }, + { url = "https://files.pythonhosted.org/packages/6a/1e/76d02f8270b97269d7e3dbd45644b1785bda457b474315f8cf999525a193/multidict-6.7.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2397ab4daaf2698eb51a76721e98db21ce4f52339e535725de03ea962b5a3202", size = 264112, upload-time = "2025-10-06T14:51:01.183Z" }, + { url = "https://files.pythonhosted.org/packages/76/0b/c28a70ecb58963847c2a8efe334904cd254812b10e535aefb3bcce513918/multidict-6.7.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8891681594162635948a636c9fe0ff21746aeb3dd5463f6e25d9bea3a8a39ca1", size = 261194, upload-time = "2025-10-06T14:51:02.794Z" }, + { url = "https://files.pythonhosted.org/packages/b4/63/2ab26e4209773223159b83aa32721b4021ffb08102f8ac7d689c943fded1/multidict-6.7.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:18706cc31dbf402a7945916dd5cddf160251b6dab8a2c5f3d6d5a55949f676b3", size = 248510, upload-time = "2025-10-06T14:51:04.724Z" }, + { url = "https://files.pythonhosted.org/packages/93/cd/06c1fa8282af1d1c46fd55c10a7930af652afdce43999501d4d68664170c/multidict-6.7.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:f844a1bbf1d207dd311a56f383f7eda2d0e134921d45751842d8235e7778965d", size = 248395, upload-time = "2025-10-06T14:51:06.306Z" }, + { url = "https://files.pythonhosted.org/packages/99/ac/82cb419dd6b04ccf9e7e61befc00c77614fc8134362488b553402ecd55ce/multidict-6.7.0-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:d4393e3581e84e5645506923816b9cc81f5609a778c7e7534054091acc64d1c6", size = 239520, upload-time = "2025-10-06T14:51:08.091Z" }, + { url = "https://files.pythonhosted.org/packages/fa/f3/a0f9bf09493421bd8716a362e0cd1d244f5a6550f5beffdd6b47e885b331/multidict-6.7.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:fbd18dc82d7bf274b37aa48d664534330af744e03bccf696d6f4c6042e7d19e7", size = 245479, upload-time = "2025-10-06T14:51:10.365Z" }, + { url = "https://files.pythonhosted.org/packages/8d/01/476d38fc73a212843f43c852b0eee266b6971f0e28329c2184a8df90c376/multidict-6.7.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:b6234e14f9314731ec45c42fc4554b88133ad53a09092cc48a88e771c125dadb", size = 258903, upload-time = "2025-10-06T14:51:12.466Z" }, + { url = "https://files.pythonhosted.org/packages/49/6d/23faeb0868adba613b817d0e69c5f15531b24d462af8012c4f6de4fa8dc3/multidict-6.7.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:08d4379f9744d8f78d98c8673c06e202ffa88296f009c71bbafe8a6bf847d01f", size = 252333, upload-time = "2025-10-06T14:51:14.48Z" }, + { url = "https://files.pythonhosted.org/packages/1e/cc/48d02ac22b30fa247f7dad82866e4b1015431092f4ba6ebc7e77596e0b18/multidict-6.7.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:9fe04da3f79387f450fd0061d4dd2e45a72749d31bf634aecc9e27f24fdc4b3f", size = 243411, upload-time = "2025-10-06T14:51:16.072Z" }, + { url = "https://files.pythonhosted.org/packages/4a/03/29a8bf5a18abf1fe34535c88adbdfa88c9fb869b5a3b120692c64abe8284/multidict-6.7.0-cp314-cp314-win32.whl", hash = "sha256:fbafe31d191dfa7c4c51f7a6149c9fb7e914dcf9ffead27dcfd9f1ae382b3885", size = 40940, upload-time = "2025-10-06T14:51:17.544Z" }, + { url = "https://files.pythonhosted.org/packages/82/16/7ed27b680791b939de138f906d5cf2b4657b0d45ca6f5dd6236fdddafb1a/multidict-6.7.0-cp314-cp314-win_amd64.whl", hash = "sha256:2f67396ec0310764b9222a1728ced1ab638f61aadc6226f17a71dd9324f9a99c", size = 45087, upload-time = "2025-10-06T14:51:18.875Z" }, + { url = "https://files.pythonhosted.org/packages/cd/3c/e3e62eb35a1950292fe39315d3c89941e30a9d07d5d2df42965ab041da43/multidict-6.7.0-cp314-cp314-win_arm64.whl", hash = "sha256:ba672b26069957ee369cfa7fc180dde1fc6f176eaf1e6beaf61fbebbd3d9c000", size = 42368, upload-time = "2025-10-06T14:51:20.225Z" }, + { url = "https://files.pythonhosted.org/packages/8b/40/cd499bd0dbc5f1136726db3153042a735fffd0d77268e2ee20d5f33c010f/multidict-6.7.0-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:c1dcc7524066fa918c6a27d61444d4ee7900ec635779058571f70d042d86ed63", size = 82326, upload-time = "2025-10-06T14:51:21.588Z" }, + { url = "https://files.pythonhosted.org/packages/13/8a/18e031eca251c8df76daf0288e6790561806e439f5ce99a170b4af30676b/multidict-6.7.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:27e0b36c2d388dc7b6ced3406671b401e84ad7eb0656b8f3a2f46ed0ce483718", size = 48065, upload-time = "2025-10-06T14:51:22.93Z" }, + { url = "https://files.pythonhosted.org/packages/40/71/5e6701277470a87d234e433fb0a3a7deaf3bcd92566e421e7ae9776319de/multidict-6.7.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:2a7baa46a22e77f0988e3b23d4ede5513ebec1929e34ee9495be535662c0dfe2", size = 46475, upload-time = "2025-10-06T14:51:24.352Z" }, + { url = "https://files.pythonhosted.org/packages/fe/6a/bab00cbab6d9cfb57afe1663318f72ec28289ea03fd4e8236bb78429893a/multidict-6.7.0-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:7bf77f54997a9166a2f5675d1201520586439424c2511723a7312bdb4bcc034e", size = 239324, upload-time = "2025-10-06T14:51:25.822Z" }, + { url = "https://files.pythonhosted.org/packages/2a/5f/8de95f629fc22a7769ade8b41028e3e5a822c1f8904f618d175945a81ad3/multidict-6.7.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e011555abada53f1578d63389610ac8a5400fc70ce71156b0aa30d326f1a5064", size = 246877, upload-time = "2025-10-06T14:51:27.604Z" }, + { url = "https://files.pythonhosted.org/packages/23/b4/38881a960458f25b89e9f4a4fdcb02ac101cfa710190db6e5528841e67de/multidict-6.7.0-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:28b37063541b897fd6a318007373930a75ca6d6ac7c940dbe14731ffdd8d498e", size = 225824, upload-time = "2025-10-06T14:51:29.664Z" }, + { url = "https://files.pythonhosted.org/packages/1e/39/6566210c83f8a261575f18e7144736059f0c460b362e96e9cf797a24b8e7/multidict-6.7.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:05047ada7a2fde2631a0ed706f1fd68b169a681dfe5e4cf0f8e4cb6618bbc2cd", size = 253558, upload-time = "2025-10-06T14:51:31.684Z" }, + { url = "https://files.pythonhosted.org/packages/00/a3/67f18315100f64c269f46e6c0319fa87ba68f0f64f2b8e7fd7c72b913a0b/multidict-6.7.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:716133f7d1d946a4e1b91b1756b23c088881e70ff180c24e864c26192ad7534a", size = 252339, upload-time = "2025-10-06T14:51:33.699Z" }, + { url = "https://files.pythonhosted.org/packages/c8/2a/1cb77266afee2458d82f50da41beba02159b1d6b1f7973afc9a1cad1499b/multidict-6.7.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d1bed1b467ef657f2a0ae62844a607909ef1c6889562de5e1d505f74457d0b96", size = 244895, upload-time = "2025-10-06T14:51:36.189Z" }, + { url = "https://files.pythonhosted.org/packages/dd/72/09fa7dd487f119b2eb9524946ddd36e2067c08510576d43ff68469563b3b/multidict-6.7.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:ca43bdfa5d37bd6aee89d85e1d0831fb86e25541be7e9d376ead1b28974f8e5e", size = 241862, upload-time = "2025-10-06T14:51:41.291Z" }, + { url = "https://files.pythonhosted.org/packages/65/92/bc1f8bd0853d8669300f732c801974dfc3702c3eeadae2f60cef54dc69d7/multidict-6.7.0-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:44b546bd3eb645fd26fb949e43c02a25a2e632e2ca21a35e2e132c8105dc8599", size = 232376, upload-time = "2025-10-06T14:51:43.55Z" }, + { url = "https://files.pythonhosted.org/packages/09/86/ac39399e5cb9d0c2ac8ef6e10a768e4d3bc933ac808d49c41f9dc23337eb/multidict-6.7.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:a6ef16328011d3f468e7ebc326f24c1445f001ca1dec335b2f8e66bed3006394", size = 240272, upload-time = "2025-10-06T14:51:45.265Z" }, + { url = "https://files.pythonhosted.org/packages/3d/b6/fed5ac6b8563ec72df6cb1ea8dac6d17f0a4a1f65045f66b6d3bf1497c02/multidict-6.7.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:5aa873cbc8e593d361ae65c68f85faadd755c3295ea2c12040ee146802f23b38", size = 248774, upload-time = "2025-10-06T14:51:46.836Z" }, + { url = "https://files.pythonhosted.org/packages/6b/8d/b954d8c0dc132b68f760aefd45870978deec6818897389dace00fcde32ff/multidict-6.7.0-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:3d7b6ccce016e29df4b7ca819659f516f0bc7a4b3efa3bb2012ba06431b044f9", size = 242731, upload-time = "2025-10-06T14:51:48.541Z" }, + { url = "https://files.pythonhosted.org/packages/16/9d/a2dac7009125d3540c2f54e194829ea18ac53716c61b655d8ed300120b0f/multidict-6.7.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:171b73bd4ee683d307599b66793ac80981b06f069b62eea1c9e29c9241aa66b0", size = 240193, upload-time = "2025-10-06T14:51:50.355Z" }, + { url = "https://files.pythonhosted.org/packages/39/ca/c05f144128ea232ae2178b008d5011d4e2cea86e4ee8c85c2631b1b94802/multidict-6.7.0-cp314-cp314t-win32.whl", hash = "sha256:b2d7f80c4e1fd010b07cb26820aae86b7e73b681ee4889684fb8d2d4537aab13", size = 48023, upload-time = "2025-10-06T14:51:51.883Z" }, + { url = "https://files.pythonhosted.org/packages/ba/8f/0a60e501584145588be1af5cc829265701ba3c35a64aec8e07cbb71d39bb/multidict-6.7.0-cp314-cp314t-win_amd64.whl", hash = "sha256:09929cab6fcb68122776d575e03c6cc64ee0b8fca48d17e135474b042ce515cd", size = 53507, upload-time = "2025-10-06T14:51:53.672Z" }, + { url = "https://files.pythonhosted.org/packages/7f/ae/3148b988a9c6239903e786eac19c889fab607c31d6efa7fb2147e5680f23/multidict-6.7.0-cp314-cp314t-win_arm64.whl", hash = "sha256:cc41db090ed742f32bd2d2c721861725e6109681eddf835d0a82bd3a5c382827", size = 44804, upload-time = "2025-10-06T14:51:55.415Z" }, + { url = "https://files.pythonhosted.org/packages/b7/da/7d22601b625e241d4f23ef1ebff8acfc60da633c9e7e7922e24d10f592b3/multidict-6.7.0-py3-none-any.whl", hash = "sha256:394fc5c42a333c9ffc3e421a4c85e08580d990e08b99f6bf35b4132114c5dcb3", size = 12317, upload-time = "2025-10-06T14:52:29.272Z" }, +] + [[package]] name = "mypy" version = "1.11.2" @@ -670,6 +981,101 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/88/5f/e351af9a41f866ac3f1fac4ca0613908d9a41741cfcf2228f4ad853b697d/pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669", size = 20556, upload-time = "2024-04-20T21:34:40.434Z" }, ] +[[package]] +name = "propcache" +version = "0.4.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/9e/da/e9fc233cf63743258bff22b3dfa7ea5baef7b5bc324af47a0ad89b8ffc6f/propcache-0.4.1.tar.gz", hash = "sha256:f48107a8c637e80362555f37ecf49abe20370e557cc4ab374f04ec4423c97c3d", size = 46442, upload-time = "2025-10-08T19:49:02.291Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bf/df/6d9c1b6ac12b003837dde8a10231a7344512186e87b36e855bef32241942/propcache-0.4.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:43eedf29202c08550aac1d14e0ee619b0430aaef78f85864c1a892294fbc28cf", size = 77750, upload-time = "2025-10-08T19:47:07.648Z" }, + { url = "https://files.pythonhosted.org/packages/8b/e8/677a0025e8a2acf07d3418a2e7ba529c9c33caf09d3c1f25513023c1db56/propcache-0.4.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:d62cdfcfd89ccb8de04e0eda998535c406bf5e060ffd56be6c586cbcc05b3311", size = 44780, upload-time = "2025-10-08T19:47:08.851Z" }, + { url = "https://files.pythonhosted.org/packages/89/a4/92380f7ca60f99ebae761936bc48a72a639e8a47b29050615eef757cb2a7/propcache-0.4.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:cae65ad55793da34db5f54e4029b89d3b9b9490d8abe1b4c7ab5d4b8ec7ebf74", size = 46308, upload-time = "2025-10-08T19:47:09.982Z" }, + { url = "https://files.pythonhosted.org/packages/2d/48/c5ac64dee5262044348d1d78a5f85dd1a57464a60d30daee946699963eb3/propcache-0.4.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:333ddb9031d2704a301ee3e506dc46b1fe5f294ec198ed6435ad5b6a085facfe", size = 208182, upload-time = "2025-10-08T19:47:11.319Z" }, + { url = "https://files.pythonhosted.org/packages/c6/0c/cd762dd011a9287389a6a3eb43aa30207bde253610cca06824aeabfe9653/propcache-0.4.1-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:fd0858c20f078a32cf55f7e81473d96dcf3b93fd2ccdb3d40fdf54b8573df3af", size = 211215, upload-time = "2025-10-08T19:47:13.146Z" }, + { url = "https://files.pythonhosted.org/packages/30/3e/49861e90233ba36890ae0ca4c660e95df565b2cd15d4a68556ab5865974e/propcache-0.4.1-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:678ae89ebc632c5c204c794f8dab2837c5f159aeb59e6ed0539500400577298c", size = 218112, upload-time = "2025-10-08T19:47:14.913Z" }, + { url = "https://files.pythonhosted.org/packages/f1/8b/544bc867e24e1bd48f3118cecd3b05c694e160a168478fa28770f22fd094/propcache-0.4.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d472aeb4fbf9865e0c6d622d7f4d54a4e101a89715d8904282bb5f9a2f476c3f", size = 204442, upload-time = "2025-10-08T19:47:16.277Z" }, + { url = "https://files.pythonhosted.org/packages/50/a6/4282772fd016a76d3e5c0df58380a5ea64900afd836cec2c2f662d1b9bb3/propcache-0.4.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4d3df5fa7e36b3225954fba85589da77a0fe6a53e3976de39caf04a0db4c36f1", size = 199398, upload-time = "2025-10-08T19:47:17.962Z" }, + { url = "https://files.pythonhosted.org/packages/3e/ec/d8a7cd406ee1ddb705db2139f8a10a8a427100347bd698e7014351c7af09/propcache-0.4.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:ee17f18d2498f2673e432faaa71698032b0127ebf23ae5974eeaf806c279df24", size = 196920, upload-time = "2025-10-08T19:47:19.355Z" }, + { url = "https://files.pythonhosted.org/packages/f6/6c/f38ab64af3764f431e359f8baf9e0a21013e24329e8b85d2da32e8ed07ca/propcache-0.4.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:580e97762b950f993ae618e167e7be9256b8353c2dcd8b99ec100eb50f5286aa", size = 203748, upload-time = "2025-10-08T19:47:21.338Z" }, + { url = "https://files.pythonhosted.org/packages/d6/e3/fa846bd70f6534d647886621388f0a265254d30e3ce47e5c8e6e27dbf153/propcache-0.4.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:501d20b891688eb8e7aa903021f0b72d5a55db40ffaab27edefd1027caaafa61", size = 205877, upload-time = "2025-10-08T19:47:23.059Z" }, + { url = "https://files.pythonhosted.org/packages/e2/39/8163fc6f3133fea7b5f2827e8eba2029a0277ab2c5beee6c1db7b10fc23d/propcache-0.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9a0bd56e5b100aef69bd8562b74b46254e7c8812918d3baa700c8a8009b0af66", size = 199437, upload-time = "2025-10-08T19:47:24.445Z" }, + { url = "https://files.pythonhosted.org/packages/93/89/caa9089970ca49c7c01662bd0eeedfe85494e863e8043565aeb6472ce8fe/propcache-0.4.1-cp313-cp313-win32.whl", hash = "sha256:bcc9aaa5d80322bc2fb24bb7accb4a30f81e90ab8d6ba187aec0744bc302ad81", size = 37586, upload-time = "2025-10-08T19:47:25.736Z" }, + { url = "https://files.pythonhosted.org/packages/f5/ab/f76ec3c3627c883215b5c8080debb4394ef5a7a29be811f786415fc1e6fd/propcache-0.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:381914df18634f5494334d201e98245c0596067504b9372d8cf93f4bb23e025e", size = 40790, upload-time = "2025-10-08T19:47:26.847Z" }, + { url = "https://files.pythonhosted.org/packages/59/1b/e71ae98235f8e2ba5004d8cb19765a74877abf189bc53fc0c80d799e56c3/propcache-0.4.1-cp313-cp313-win_arm64.whl", hash = "sha256:8873eb4460fd55333ea49b7d189749ecf6e55bf85080f11b1c4530ed3034cba1", size = 37158, upload-time = "2025-10-08T19:47:27.961Z" }, + { url = "https://files.pythonhosted.org/packages/83/ce/a31bbdfc24ee0dcbba458c8175ed26089cf109a55bbe7b7640ed2470cfe9/propcache-0.4.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:92d1935ee1f8d7442da9c0c4fa7ac20d07e94064184811b685f5c4fada64553b", size = 81451, upload-time = "2025-10-08T19:47:29.445Z" }, + { url = "https://files.pythonhosted.org/packages/25/9c/442a45a470a68456e710d96cacd3573ef26a1d0a60067e6a7d5e655621ed/propcache-0.4.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:473c61b39e1460d386479b9b2f337da492042447c9b685f28be4f74d3529e566", size = 46374, upload-time = "2025-10-08T19:47:30.579Z" }, + { url = "https://files.pythonhosted.org/packages/f4/bf/b1d5e21dbc3b2e889ea4327044fb16312a736d97640fb8b6aa3f9c7b3b65/propcache-0.4.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:c0ef0aaafc66fbd87842a3fe3902fd889825646bc21149eafe47be6072725835", size = 48396, upload-time = "2025-10-08T19:47:31.79Z" }, + { url = "https://files.pythonhosted.org/packages/f4/04/5b4c54a103d480e978d3c8a76073502b18db0c4bc17ab91b3cb5092ad949/propcache-0.4.1-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f95393b4d66bfae908c3ca8d169d5f79cd65636ae15b5e7a4f6e67af675adb0e", size = 275950, upload-time = "2025-10-08T19:47:33.481Z" }, + { url = "https://files.pythonhosted.org/packages/b4/c1/86f846827fb969c4b78b0af79bba1d1ea2156492e1b83dea8b8a6ae27395/propcache-0.4.1-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c07fda85708bc48578467e85099645167a955ba093be0a2dcba962195676e859", size = 273856, upload-time = "2025-10-08T19:47:34.906Z" }, + { url = "https://files.pythonhosted.org/packages/36/1d/fc272a63c8d3bbad6878c336c7a7dea15e8f2d23a544bda43205dfa83ada/propcache-0.4.1-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:af223b406d6d000830c6f65f1e6431783fc3f713ba3e6cc8c024d5ee96170a4b", size = 280420, upload-time = "2025-10-08T19:47:36.338Z" }, + { url = "https://files.pythonhosted.org/packages/07/0c/01f2219d39f7e53d52e5173bcb09c976609ba30209912a0680adfb8c593a/propcache-0.4.1-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a78372c932c90ee474559c5ddfffd718238e8673c340dc21fe45c5b8b54559a0", size = 263254, upload-time = "2025-10-08T19:47:37.692Z" }, + { url = "https://files.pythonhosted.org/packages/2d/18/cd28081658ce597898f0c4d174d4d0f3c5b6d4dc27ffafeef835c95eb359/propcache-0.4.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:564d9f0d4d9509e1a870c920a89b2fec951b44bf5ba7d537a9e7c1ccec2c18af", size = 261205, upload-time = "2025-10-08T19:47:39.659Z" }, + { url = "https://files.pythonhosted.org/packages/7a/71/1f9e22eb8b8316701c2a19fa1f388c8a3185082607da8e406a803c9b954e/propcache-0.4.1-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:17612831fda0138059cc5546f4d12a2aacfb9e47068c06af35c400ba58ba7393", size = 247873, upload-time = "2025-10-08T19:47:41.084Z" }, + { url = "https://files.pythonhosted.org/packages/4a/65/3d4b61f36af2b4eddba9def857959f1016a51066b4f1ce348e0cf7881f58/propcache-0.4.1-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:41a89040cb10bd345b3c1a873b2bf36413d48da1def52f268a055f7398514874", size = 262739, upload-time = "2025-10-08T19:47:42.51Z" }, + { url = "https://files.pythonhosted.org/packages/2a/42/26746ab087faa77c1c68079b228810436ccd9a5ce9ac85e2b7307195fd06/propcache-0.4.1-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:e35b88984e7fa64aacecea39236cee32dd9bd8c55f57ba8a75cf2399553f9bd7", size = 263514, upload-time = "2025-10-08T19:47:43.927Z" }, + { url = "https://files.pythonhosted.org/packages/94/13/630690fe201f5502d2403dd3cfd451ed8858fe3c738ee88d095ad2ff407b/propcache-0.4.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:6f8b465489f927b0df505cbe26ffbeed4d6d8a2bbc61ce90eb074ff129ef0ab1", size = 257781, upload-time = "2025-10-08T19:47:45.448Z" }, + { url = "https://files.pythonhosted.org/packages/92/f7/1d4ec5841505f423469efbfc381d64b7b467438cd5a4bbcbb063f3b73d27/propcache-0.4.1-cp313-cp313t-win32.whl", hash = "sha256:2ad890caa1d928c7c2965b48f3a3815c853180831d0e5503d35cf00c472f4717", size = 41396, upload-time = "2025-10-08T19:47:47.202Z" }, + { url = "https://files.pythonhosted.org/packages/48/f0/615c30622316496d2cbbc29f5985f7777d3ada70f23370608c1d3e081c1f/propcache-0.4.1-cp313-cp313t-win_amd64.whl", hash = "sha256:f7ee0e597f495cf415bcbd3da3caa3bd7e816b74d0d52b8145954c5e6fd3ff37", size = 44897, upload-time = "2025-10-08T19:47:48.336Z" }, + { url = "https://files.pythonhosted.org/packages/fd/ca/6002e46eccbe0e33dcd4069ef32f7f1c9e243736e07adca37ae8c4830ec3/propcache-0.4.1-cp313-cp313t-win_arm64.whl", hash = "sha256:929d7cbe1f01bb7baffb33dc14eb5691c95831450a26354cd210a8155170c93a", size = 39789, upload-time = "2025-10-08T19:47:49.876Z" }, + { url = "https://files.pythonhosted.org/packages/8e/5c/bca52d654a896f831b8256683457ceddd490ec18d9ec50e97dfd8fc726a8/propcache-0.4.1-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:3f7124c9d820ba5548d431afb4632301acf965db49e666aa21c305cbe8c6de12", size = 78152, upload-time = "2025-10-08T19:47:51.051Z" }, + { url = "https://files.pythonhosted.org/packages/65/9b/03b04e7d82a5f54fb16113d839f5ea1ede58a61e90edf515f6577c66fa8f/propcache-0.4.1-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:c0d4b719b7da33599dfe3b22d3db1ef789210a0597bc650b7cee9c77c2be8c5c", size = 44869, upload-time = "2025-10-08T19:47:52.594Z" }, + { url = "https://files.pythonhosted.org/packages/b2/fa/89a8ef0468d5833a23fff277b143d0573897cf75bd56670a6d28126c7d68/propcache-0.4.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:9f302f4783709a78240ebc311b793f123328716a60911d667e0c036bc5dcbded", size = 46596, upload-time = "2025-10-08T19:47:54.073Z" }, + { url = "https://files.pythonhosted.org/packages/86/bd/47816020d337f4a746edc42fe8d53669965138f39ee117414c7d7a340cfe/propcache-0.4.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c80ee5802e3fb9ea37938e7eecc307fb984837091d5fd262bb37238b1ae97641", size = 206981, upload-time = "2025-10-08T19:47:55.715Z" }, + { url = "https://files.pythonhosted.org/packages/df/f6/c5fa1357cc9748510ee55f37173eb31bfde6d94e98ccd9e6f033f2fc06e1/propcache-0.4.1-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ed5a841e8bb29a55fb8159ed526b26adc5bdd7e8bd7bf793ce647cb08656cdf4", size = 211490, upload-time = "2025-10-08T19:47:57.499Z" }, + { url = "https://files.pythonhosted.org/packages/80/1e/e5889652a7c4a3846683401a48f0f2e5083ce0ec1a8a5221d8058fbd1adf/propcache-0.4.1-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:55c72fd6ea2da4c318e74ffdf93c4fe4e926051133657459131a95c846d16d44", size = 215371, upload-time = "2025-10-08T19:47:59.317Z" }, + { url = "https://files.pythonhosted.org/packages/b2/f2/889ad4b2408f72fe1a4f6a19491177b30ea7bf1a0fd5f17050ca08cfc882/propcache-0.4.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8326e144341460402713f91df60ade3c999d601e7eb5ff8f6f7862d54de0610d", size = 201424, upload-time = "2025-10-08T19:48:00.67Z" }, + { url = "https://files.pythonhosted.org/packages/27/73/033d63069b57b0812c8bd19f311faebeceb6ba31b8f32b73432d12a0b826/propcache-0.4.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:060b16ae65bc098da7f6d25bf359f1f31f688384858204fe5d652979e0015e5b", size = 197566, upload-time = "2025-10-08T19:48:02.604Z" }, + { url = "https://files.pythonhosted.org/packages/dc/89/ce24f3dc182630b4e07aa6d15f0ff4b14ed4b9955fae95a0b54c58d66c05/propcache-0.4.1-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:89eb3fa9524f7bec9de6e83cf3faed9d79bffa560672c118a96a171a6f55831e", size = 193130, upload-time = "2025-10-08T19:48:04.499Z" }, + { url = "https://files.pythonhosted.org/packages/a9/24/ef0d5fd1a811fb5c609278d0209c9f10c35f20581fcc16f818da959fc5b4/propcache-0.4.1-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:dee69d7015dc235f526fe80a9c90d65eb0039103fe565776250881731f06349f", size = 202625, upload-time = "2025-10-08T19:48:06.213Z" }, + { url = "https://files.pythonhosted.org/packages/f5/02/98ec20ff5546f68d673df2f7a69e8c0d076b5abd05ca882dc7ee3a83653d/propcache-0.4.1-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:5558992a00dfd54ccbc64a32726a3357ec93825a418a401f5cc67df0ac5d9e49", size = 204209, upload-time = "2025-10-08T19:48:08.432Z" }, + { url = "https://files.pythonhosted.org/packages/a0/87/492694f76759b15f0467a2a93ab68d32859672b646aa8a04ce4864e7932d/propcache-0.4.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:c9b822a577f560fbd9554812526831712c1436d2c046cedee4c3796d3543b144", size = 197797, upload-time = "2025-10-08T19:48:09.968Z" }, + { url = "https://files.pythonhosted.org/packages/ee/36/66367de3575db1d2d3f3d177432bd14ee577a39d3f5d1b3d5df8afe3b6e2/propcache-0.4.1-cp314-cp314-win32.whl", hash = "sha256:ab4c29b49d560fe48b696cdcb127dd36e0bc2472548f3bf56cc5cb3da2b2984f", size = 38140, upload-time = "2025-10-08T19:48:11.232Z" }, + { url = "https://files.pythonhosted.org/packages/0c/2a/a758b47de253636e1b8aef181c0b4f4f204bf0dd964914fb2af90a95b49b/propcache-0.4.1-cp314-cp314-win_amd64.whl", hash = "sha256:5a103c3eb905fcea0ab98be99c3a9a5ab2de60228aa5aceedc614c0281cf6153", size = 41257, upload-time = "2025-10-08T19:48:12.707Z" }, + { url = "https://files.pythonhosted.org/packages/34/5e/63bd5896c3fec12edcbd6f12508d4890d23c265df28c74b175e1ef9f4f3b/propcache-0.4.1-cp314-cp314-win_arm64.whl", hash = "sha256:74c1fb26515153e482e00177a1ad654721bf9207da8a494a0c05e797ad27b992", size = 38097, upload-time = "2025-10-08T19:48:13.923Z" }, + { url = "https://files.pythonhosted.org/packages/99/85/9ff785d787ccf9bbb3f3106f79884a130951436f58392000231b4c737c80/propcache-0.4.1-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:824e908bce90fb2743bd6b59db36eb4f45cd350a39637c9f73b1c1ea66f5b75f", size = 81455, upload-time = "2025-10-08T19:48:15.16Z" }, + { url = "https://files.pythonhosted.org/packages/90/85/2431c10c8e7ddb1445c1f7c4b54d886e8ad20e3c6307e7218f05922cad67/propcache-0.4.1-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:c2b5e7db5328427c57c8e8831abda175421b709672f6cfc3d630c3b7e2146393", size = 46372, upload-time = "2025-10-08T19:48:16.424Z" }, + { url = "https://files.pythonhosted.org/packages/01/20/b0972d902472da9bcb683fa595099911f4d2e86e5683bcc45de60dd05dc3/propcache-0.4.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:6f6ff873ed40292cd4969ef5310179afd5db59fdf055897e282485043fc80ad0", size = 48411, upload-time = "2025-10-08T19:48:17.577Z" }, + { url = "https://files.pythonhosted.org/packages/e2/e3/7dc89f4f21e8f99bad3d5ddb3a3389afcf9da4ac69e3deb2dcdc96e74169/propcache-0.4.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:49a2dc67c154db2c1463013594c458881a069fcf98940e61a0569016a583020a", size = 275712, upload-time = "2025-10-08T19:48:18.901Z" }, + { url = "https://files.pythonhosted.org/packages/20/67/89800c8352489b21a8047c773067644e3897f02ecbbd610f4d46b7f08612/propcache-0.4.1-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:005f08e6a0529984491e37d8dbc3dd86f84bd78a8ceb5fa9a021f4c48d4984be", size = 273557, upload-time = "2025-10-08T19:48:20.762Z" }, + { url = "https://files.pythonhosted.org/packages/e2/a1/b52b055c766a54ce6d9c16d9aca0cad8059acd9637cdf8aa0222f4a026ef/propcache-0.4.1-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5c3310452e0d31390da9035c348633b43d7e7feb2e37be252be6da45abd1abcc", size = 280015, upload-time = "2025-10-08T19:48:22.592Z" }, + { url = "https://files.pythonhosted.org/packages/48/c8/33cee30bd890672c63743049f3c9e4be087e6780906bfc3ec58528be59c1/propcache-0.4.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4c3c70630930447f9ef1caac7728c8ad1c56bc5015338b20fed0d08ea2480b3a", size = 262880, upload-time = "2025-10-08T19:48:23.947Z" }, + { url = "https://files.pythonhosted.org/packages/0c/b1/8f08a143b204b418285c88b83d00edbd61afbc2c6415ffafc8905da7038b/propcache-0.4.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8e57061305815dfc910a3634dcf584f08168a8836e6999983569f51a8544cd89", size = 260938, upload-time = "2025-10-08T19:48:25.656Z" }, + { url = "https://files.pythonhosted.org/packages/cf/12/96e4664c82ca2f31e1c8dff86afb867348979eb78d3cb8546a680287a1e9/propcache-0.4.1-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:521a463429ef54143092c11a77e04056dd00636f72e8c45b70aaa3140d639726", size = 247641, upload-time = "2025-10-08T19:48:27.207Z" }, + { url = "https://files.pythonhosted.org/packages/18/ed/e7a9cfca28133386ba52278136d42209d3125db08d0a6395f0cba0c0285c/propcache-0.4.1-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:120c964da3fdc75e3731aa392527136d4ad35868cc556fd09bb6d09172d9a367", size = 262510, upload-time = "2025-10-08T19:48:28.65Z" }, + { url = "https://files.pythonhosted.org/packages/f5/76/16d8bf65e8845dd62b4e2b57444ab81f07f40caa5652b8969b87ddcf2ef6/propcache-0.4.1-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:d8f353eb14ee3441ee844ade4277d560cdd68288838673273b978e3d6d2c8f36", size = 263161, upload-time = "2025-10-08T19:48:30.133Z" }, + { url = "https://files.pythonhosted.org/packages/e7/70/c99e9edb5d91d5ad8a49fa3c1e8285ba64f1476782fed10ab251ff413ba1/propcache-0.4.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ab2943be7c652f09638800905ee1bab2c544e537edb57d527997a24c13dc1455", size = 257393, upload-time = "2025-10-08T19:48:31.567Z" }, + { url = "https://files.pythonhosted.org/packages/08/02/87b25304249a35c0915d236575bc3574a323f60b47939a2262b77632a3ee/propcache-0.4.1-cp314-cp314t-win32.whl", hash = "sha256:05674a162469f31358c30bcaa8883cb7829fa3110bf9c0991fe27d7896c42d85", size = 42546, upload-time = "2025-10-08T19:48:32.872Z" }, + { url = "https://files.pythonhosted.org/packages/cb/ef/3c6ecf8b317aa982f309835e8f96987466123c6e596646d4e6a1dfcd080f/propcache-0.4.1-cp314-cp314t-win_amd64.whl", hash = "sha256:990f6b3e2a27d683cb7602ed6c86f15ee6b43b1194736f9baaeb93d0016633b1", size = 46259, upload-time = "2025-10-08T19:48:34.226Z" }, + { url = "https://files.pythonhosted.org/packages/c4/2d/346e946d4951f37eca1e4f55be0f0174c52cd70720f84029b02f296f4a38/propcache-0.4.1-cp314-cp314t-win_arm64.whl", hash = "sha256:ecef2343af4cc68e05131e45024ba34f6095821988a9d0a02aa7c73fcc448aa9", size = 40428, upload-time = "2025-10-08T19:48:35.441Z" }, + { url = "https://files.pythonhosted.org/packages/5b/5a/bc7b4a4ef808fa59a816c17b20c4bef6884daebbdf627ff2a161da67da19/propcache-0.4.1-py3-none-any.whl", hash = "sha256:af2a6052aeb6cf17d3e46ee169099044fd8224cbaf75c76a2ef596e8163e2237", size = 13305, upload-time = "2025-10-08T19:49:00.792Z" }, +] + +[[package]] +name = "psutil" +version = "7.1.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e1/88/bdd0a41e5857d5d703287598cbf08dad90aed56774ea52ae071bae9071b6/psutil-7.1.3.tar.gz", hash = "sha256:6c86281738d77335af7aec228328e944b30930899ea760ecf33a4dba66be5e74", size = 489059, upload-time = "2025-11-02T12:25:54.619Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bd/93/0c49e776b8734fef56ec9c5c57f923922f2cf0497d62e0f419465f28f3d0/psutil-7.1.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0005da714eee687b4b8decd3d6cc7c6db36215c9e74e5ad2264b90c3df7d92dc", size = 239751, upload-time = "2025-11-02T12:25:58.161Z" }, + { url = "https://files.pythonhosted.org/packages/6f/8d/b31e39c769e70780f007969815195a55c81a63efebdd4dbe9e7a113adb2f/psutil-7.1.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:19644c85dcb987e35eeeaefdc3915d059dac7bd1167cdcdbf27e0ce2df0c08c0", size = 240368, upload-time = "2025-11-02T12:26:00.491Z" }, + { url = "https://files.pythonhosted.org/packages/62/61/23fd4acc3c9eebbf6b6c78bcd89e5d020cfde4acf0a9233e9d4e3fa698b4/psutil-7.1.3-cp313-cp313t-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:95ef04cf2e5ba0ab9eaafc4a11eaae91b44f4ef5541acd2ee91d9108d00d59a7", size = 287134, upload-time = "2025-11-02T12:26:02.613Z" }, + { url = "https://files.pythonhosted.org/packages/30/1c/f921a009ea9ceb51aa355cb0cc118f68d354db36eae18174bab63affb3e6/psutil-7.1.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1068c303be3a72f8e18e412c5b2a8f6d31750fb152f9cb106b54090296c9d251", size = 289904, upload-time = "2025-11-02T12:26:05.207Z" }, + { url = "https://files.pythonhosted.org/packages/a6/82/62d68066e13e46a5116df187d319d1724b3f437ddd0f958756fc052677f4/psutil-7.1.3-cp313-cp313t-win_amd64.whl", hash = "sha256:18349c5c24b06ac5612c0428ec2a0331c26443d259e2a0144a9b24b4395b58fa", size = 249642, upload-time = "2025-11-02T12:26:07.447Z" }, + { url = "https://files.pythonhosted.org/packages/df/ad/c1cd5fe965c14a0392112f68362cfceb5230819dbb5b1888950d18a11d9f/psutil-7.1.3-cp313-cp313t-win_arm64.whl", hash = "sha256:c525ffa774fe4496282fb0b1187725793de3e7c6b29e41562733cae9ada151ee", size = 245518, upload-time = "2025-11-02T12:26:09.719Z" }, + { url = "https://files.pythonhosted.org/packages/2e/bb/6670bded3e3236eb4287c7bcdc167e9fae6e1e9286e437f7111caed2f909/psutil-7.1.3-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:b403da1df4d6d43973dc004d19cee3b848e998ae3154cc8097d139b77156c353", size = 239843, upload-time = "2025-11-02T12:26:11.968Z" }, + { url = "https://files.pythonhosted.org/packages/b8/66/853d50e75a38c9a7370ddbeefabdd3d3116b9c31ef94dc92c6729bc36bec/psutil-7.1.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:ad81425efc5e75da3f39b3e636293360ad8d0b49bed7df824c79764fb4ba9b8b", size = 240369, upload-time = "2025-11-02T12:26:14.358Z" }, + { url = "https://files.pythonhosted.org/packages/41/bd/313aba97cb5bfb26916dc29cf0646cbe4dd6a89ca69e8c6edce654876d39/psutil-7.1.3-cp314-cp314t-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8f33a3702e167783a9213db10ad29650ebf383946e91bc77f28a5eb083496bc9", size = 288210, upload-time = "2025-11-02T12:26:16.699Z" }, + { url = "https://files.pythonhosted.org/packages/c2/fa/76e3c06e760927a0cfb5705eb38164254de34e9bd86db656d4dbaa228b04/psutil-7.1.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fac9cd332c67f4422504297889da5ab7e05fd11e3c4392140f7370f4208ded1f", size = 291182, upload-time = "2025-11-02T12:26:18.848Z" }, + { url = "https://files.pythonhosted.org/packages/0f/1d/5774a91607035ee5078b8fd747686ebec28a962f178712de100d00b78a32/psutil-7.1.3-cp314-cp314t-win_amd64.whl", hash = "sha256:3792983e23b69843aea49c8f5b8f115572c5ab64c153bada5270086a2123c7e7", size = 250466, upload-time = "2025-11-02T12:26:21.183Z" }, + { url = "https://files.pythonhosted.org/packages/00/ca/e426584bacb43a5cb1ac91fae1937f478cd8fbe5e4ff96574e698a2c77cd/psutil-7.1.3-cp314-cp314t-win_arm64.whl", hash = "sha256:31d77fcedb7529f27bb3a0472bea9334349f9a04160e8e6e5020f22c59893264", size = 245756, upload-time = "2025-11-02T12:26:23.148Z" }, + { url = "https://files.pythonhosted.org/packages/ef/94/46b9154a800253e7ecff5aaacdf8ebf43db99de4a2dfa18575b02548654e/psutil-7.1.3-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:2bdbcd0e58ca14996a42adf3621a6244f1bb2e2e528886959c72cf1e326677ab", size = 238359, upload-time = "2025-11-02T12:26:25.284Z" }, + { url = "https://files.pythonhosted.org/packages/68/3a/9f93cff5c025029a36d9a92fef47220ab4692ee7f2be0fba9f92813d0cb8/psutil-7.1.3-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:bc31fa00f1fbc3c3802141eede66f3a2d51d89716a194bf2cd6fc68310a19880", size = 239171, upload-time = "2025-11-02T12:26:27.23Z" }, + { url = "https://files.pythonhosted.org/packages/ce/b1/5f49af514f76431ba4eea935b8ad3725cdeb397e9245ab919dbc1d1dc20f/psutil-7.1.3-cp36-abi3-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3bb428f9f05c1225a558f53e30ccbad9930b11c3fc206836242de1091d3e7dd3", size = 263261, upload-time = "2025-11-02T12:26:29.48Z" }, + { url = "https://files.pythonhosted.org/packages/e0/95/992c8816a74016eb095e73585d747e0a8ea21a061ed3689474fabb29a395/psutil-7.1.3-cp36-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:56d974e02ca2c8eb4812c3f76c30e28836fffc311d55d979f1465c1feeb2b68b", size = 264635, upload-time = "2025-11-02T12:26:31.74Z" }, + { url = "https://files.pythonhosted.org/packages/55/4c/c3ed1a622b6ae2fd3c945a366e64eb35247a31e4db16cf5095e269e8eb3c/psutil-7.1.3-cp37-abi3-win_amd64.whl", hash = "sha256:f39c2c19fe824b47484b96f9692932248a54c43799a84282cfe58d05a6449efd", size = 247633, upload-time = "2025-11-02T12:26:33.887Z" }, + { url = "https://files.pythonhosted.org/packages/c9/ad/33b2ccec09bf96c2b2ef3f9a6f66baac8253d7565d8839e024a6b905d45d/psutil-7.1.3-cp37-abi3-win_arm64.whl", hash = "sha256:bd0d69cee829226a761e92f28140bec9a5ee9d5b4fb4b0cc589068dbfff559b1", size = 244608, upload-time = "2025-11-02T12:26:36.136Z" }, +] + [[package]] name = "pygments" version = "2.19.2" @@ -761,6 +1167,21 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/81/c4/34e93fe5f5429d7570ec1fa436f1986fb1f00c3e0f43a589fe2bbcd22c3f/pytz-2025.2-py2.py3-none-any.whl", hash = "sha256:5ddf76296dd8c44c26eb8f4b6f35488f3ccbf6fbbd7adee0b7262d43f0ec2f00", size = 509225, upload-time = "2025-03-25T02:24:58.468Z" }, ] +[[package]] +name = "requests" +version = "2.32.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "charset-normalizer" }, + { name = "idna" }, + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c9/74/b3ff8e6c8446842c3f5c837e9c3dfcfe2018ea6ecef224c710c85ef728f4/requests-2.32.5.tar.gz", hash = "sha256:dbba0bac56e100853db0ea71b82b4dfd5fe2bf6d3754a8893c3af500cec7d7cf", size = 134517, upload-time = "2025-08-18T20:46:02.573Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6", size = 64738, upload-time = "2025-08-18T20:46:00.542Z" }, +] + [[package]] name = "requirements-parser" version = "0.11.0" @@ -905,6 +1326,7 @@ dependencies = [ { name = "scipy" }, { name = "seaborn" }, { name = "torch" }, + { name = "torch-geometric" }, { name = "trimesh" }, { name = "vedo" }, ] @@ -927,6 +1349,7 @@ requires-dist = [ { name = "scipy", specifier = ">=1.16.2" }, { name = "seaborn" }, { name = "torch" }, + { name = "torch-geometric", specifier = ">=2.7.0" }, { name = "trimesh" }, { name = "vedo" }, ] @@ -980,6 +1403,38 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/04/6e/650bb7f28f771af0cb791b02348db8b7f5f64f40f6829ee82aa6ce99aabe/torch-2.8.0-cp313-none-macosx_11_0_arm64.whl", hash = "sha256:7b677e17f5a3e69fdef7eb3b9da72622f8d322692930297e4ccb52fefc6c8211", size = 73632395, upload-time = "2025-08-06T14:55:28.645Z" }, ] +[[package]] +name = "torch-geometric" +version = "2.7.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "aiohttp" }, + { name = "fsspec" }, + { name = "jinja2" }, + { name = "numpy" }, + { name = "psutil" }, + { name = "pyparsing" }, + { name = "requests" }, + { name = "tqdm" }, + { name = "xxhash" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/75/63/b210152635902da7fe79fcdd16517fae108f457a0ed22c737e702a9afbae/torch_geometric-2.7.0.tar.gz", hash = "sha256:f9099e4aece1a9f618c84dbaac33a77f43139736698c7e8bddf3301ef1f2e8d4", size = 876725, upload-time = "2025-10-15T20:48:03.443Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/d3/4dffd7300500465e0b4a2ae917dcb2ce771de0b9a772670365799a27c024/torch_geometric-2.7.0-py3-none-any.whl", hash = "sha256:6e0cd3ad824d484651ef5d308fc66c687bfcf5ba040d56d1e0fe0f81f365e292", size = 1275346, upload-time = "2025-10-15T20:48:01.949Z" }, +] + +[[package]] +name = "tqdm" +version = "4.67.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a8/4b/29b4ef32e036bb34e4ab51796dd745cdba7ed47ad142a9f4a1eb8e0c744d/tqdm-4.67.1.tar.gz", hash = "sha256:f8aef9c52c08c13a65f30ea34f4e5aac3fd1a34959879d7e59e63027286627f2", size = 169737, upload-time = "2024-11-24T20:12:22.481Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d0/30/dc54f88dd4a2b5dc8a0279bdd7270e735851848b762aeb1c1184ed1f6b14/tqdm-4.67.1-py3-none-any.whl", hash = "sha256:26445eca388f82e72884e0d580d5464cd801a3ea01e63e5601bdff9ba6a48de2", size = 78540, upload-time = "2024-11-24T20:12:19.698Z" }, +] + [[package]] name = "trimesh" version = "4.8.1" @@ -1031,6 +1486,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/5c/23/c7abc0ca0a1526a0774eca151daeb8de62ec457e77262b66b359c3c7679e/tzdata-2025.2-py2.py3-none-any.whl", hash = "sha256:1a403fada01ff9221ca8044d701868fa132215d84beb92242d9acd2147f667a8", size = 347839, upload-time = "2025-03-23T13:54:41.845Z" }, ] +[[package]] +name = "urllib3" +version = "2.5.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/15/22/9ee70a2574a4f4599c47dd506532914ce044817c7752a79b6a51286319bc/urllib3-2.5.0.tar.gz", hash = "sha256:3fc47733c7e419d4bc3f6b3dc2b4f890bb743906a30d56ba4a5bfa4bbff92760", size = 393185, upload-time = "2025-06-18T14:07:41.644Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a7/c2/fe1e52489ae3122415c51f387e221dd0773709bad6c6cdaa599e8a2c5185/urllib3-2.5.0-py3-none-any.whl", hash = "sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc", size = 129795, upload-time = "2025-06-18T14:07:40.39Z" }, +] + [[package]] name = "vedo" version = "2025.5.4" @@ -1060,3 +1524,149 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/8e/91/c2c52f39c8a39747486724018f769cabbd34a172efbc614610c65164d365/vtk-9.5.1-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:ef8fff2924923e4223cf02bd63e5e0cab5e5cd54702c0ad6eb93ac6ae603244e", size = 103785920, upload-time = "2025-08-28T19:14:32.922Z" }, { url = "https://files.pythonhosted.org/packages/9e/42/378aad4e1e7b4adaf2f3a8b1837b5d51db83a3bbd8e96c14a59031c60b61/vtk-9.5.1-cp313-cp313-win_amd64.whl", hash = "sha256:e080409228e63510ab7a6b110281cb374166c823ccc4b2e3a78a4b1cd1f4887d", size = 63993372, upload-time = "2025-08-28T19:14:37.263Z" }, ] + +[[package]] +name = "xxhash" +version = "3.6.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/02/84/30869e01909fb37a6cc7e18688ee8bf1e42d57e7e0777636bd47524c43c7/xxhash-3.6.0.tar.gz", hash = "sha256:f0162a78b13a0d7617b2845b90c763339d1f1d82bb04a4b07f4ab535cc5e05d6", size = 85160, upload-time = "2025-10-02T14:37:08.097Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/33/76/35d05267ac82f53ae9b0e554da7c5e281ee61f3cad44c743f0fcd354f211/xxhash-3.6.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:599e64ba7f67472481ceb6ee80fa3bd828fd61ba59fb11475572cc5ee52b89ec", size = 32738, upload-time = "2025-10-02T14:34:55.839Z" }, + { url = "https://files.pythonhosted.org/packages/31/a8/3fbce1cd96534a95e35d5120637bf29b0d7f5d8fa2f6374e31b4156dd419/xxhash-3.6.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7d8b8aaa30fca4f16f0c84a5c8d7ddee0e25250ec2796c973775373257dde8f1", size = 30821, upload-time = "2025-10-02T14:34:57.219Z" }, + { url = "https://files.pythonhosted.org/packages/0c/ea/d387530ca7ecfa183cb358027f1833297c6ac6098223fd14f9782cd0015c/xxhash-3.6.0-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:d597acf8506d6e7101a4a44a5e428977a51c0fadbbfd3c39650cca9253f6e5a6", size = 194127, upload-time = "2025-10-02T14:34:59.21Z" }, + { url = "https://files.pythonhosted.org/packages/ba/0c/71435dcb99874b09a43b8d7c54071e600a7481e42b3e3ce1eb5226a5711a/xxhash-3.6.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:858dc935963a33bc33490128edc1c12b0c14d9c7ebaa4e387a7869ecc4f3e263", size = 212975, upload-time = "2025-10-02T14:35:00.816Z" }, + { url = "https://files.pythonhosted.org/packages/84/7a/c2b3d071e4bb4a90b7057228a99b10d51744878f4a8a6dd643c8bd897620/xxhash-3.6.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ba284920194615cb8edf73bf52236ce2e1664ccd4a38fdb543506413529cc546", size = 212241, upload-time = "2025-10-02T14:35:02.207Z" }, + { url = "https://files.pythonhosted.org/packages/81/5f/640b6eac0128e215f177df99eadcd0f1b7c42c274ab6a394a05059694c5a/xxhash-3.6.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:4b54219177f6c6674d5378bd862c6aedf64725f70dd29c472eaae154df1a2e89", size = 445471, upload-time = "2025-10-02T14:35:03.61Z" }, + { url = "https://files.pythonhosted.org/packages/5e/1e/3c3d3ef071b051cc3abbe3721ffb8365033a172613c04af2da89d5548a87/xxhash-3.6.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:42c36dd7dbad2f5238950c377fcbf6811b1cdb1c444fab447960030cea60504d", size = 193936, upload-time = "2025-10-02T14:35:05.013Z" }, + { url = "https://files.pythonhosted.org/packages/2c/bd/4a5f68381939219abfe1c22a9e3a5854a4f6f6f3c4983a87d255f21f2e5d/xxhash-3.6.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f22927652cba98c44639ffdc7aaf35828dccf679b10b31c4ad72a5b530a18eb7", size = 210440, upload-time = "2025-10-02T14:35:06.239Z" }, + { url = "https://files.pythonhosted.org/packages/eb/37/b80fe3d5cfb9faff01a02121a0f4d565eb7237e9e5fc66e73017e74dcd36/xxhash-3.6.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:b45fad44d9c5c119e9c6fbf2e1c656a46dc68e280275007bbfd3d572b21426db", size = 197990, upload-time = "2025-10-02T14:35:07.735Z" }, + { url = "https://files.pythonhosted.org/packages/d7/fd/2c0a00c97b9e18f72e1f240ad4e8f8a90fd9d408289ba9c7c495ed7dc05c/xxhash-3.6.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:6f2580ffab1a8b68ef2b901cde7e55fa8da5e4be0977c68f78fc80f3c143de42", size = 210689, upload-time = "2025-10-02T14:35:09.438Z" }, + { url = "https://files.pythonhosted.org/packages/93/86/5dd8076a926b9a95db3206aba20d89a7fc14dd5aac16e5c4de4b56033140/xxhash-3.6.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:40c391dd3cd041ebc3ffe6f2c862f402e306eb571422e0aa918d8070ba31da11", size = 414068, upload-time = "2025-10-02T14:35:11.162Z" }, + { url = "https://files.pythonhosted.org/packages/af/3c/0bb129170ee8f3650f08e993baee550a09593462a5cddd8e44d0011102b1/xxhash-3.6.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f205badabde7aafd1a31e8ca2a3e5a763107a71c397c4481d6a804eb5063d8bd", size = 191495, upload-time = "2025-10-02T14:35:12.971Z" }, + { url = "https://files.pythonhosted.org/packages/e9/3a/6797e0114c21d1725e2577508e24006fd7ff1d8c0c502d3b52e45c1771d8/xxhash-3.6.0-cp313-cp313-win32.whl", hash = "sha256:2577b276e060b73b73a53042ea5bd5203d3e6347ce0d09f98500f418a9fcf799", size = 30620, upload-time = "2025-10-02T14:35:14.129Z" }, + { url = "https://files.pythonhosted.org/packages/86/15/9bc32671e9a38b413a76d24722a2bf8784a132c043063a8f5152d390b0f9/xxhash-3.6.0-cp313-cp313-win_amd64.whl", hash = "sha256:757320d45d2fbcce8f30c42a6b2f47862967aea7bf458b9625b4bbe7ee390392", size = 31542, upload-time = "2025-10-02T14:35:15.21Z" }, + { url = "https://files.pythonhosted.org/packages/39/c5/cc01e4f6188656e56112d6a8e0dfe298a16934b8c47a247236549a3f7695/xxhash-3.6.0-cp313-cp313-win_arm64.whl", hash = "sha256:457b8f85dec5825eed7b69c11ae86834a018b8e3df5e77783c999663da2f96d6", size = 27880, upload-time = "2025-10-02T14:35:16.315Z" }, + { url = "https://files.pythonhosted.org/packages/f3/30/25e5321c8732759e930c555176d37e24ab84365482d257c3b16362235212/xxhash-3.6.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:a42e633d75cdad6d625434e3468126c73f13f7584545a9cf34e883aa1710e702", size = 32956, upload-time = "2025-10-02T14:35:17.413Z" }, + { url = "https://files.pythonhosted.org/packages/9f/3c/0573299560d7d9f8ab1838f1efc021a280b5ae5ae2e849034ef3dee18810/xxhash-3.6.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:568a6d743219e717b07b4e03b0a828ce593833e498c3b64752e0f5df6bfe84db", size = 31072, upload-time = "2025-10-02T14:35:18.844Z" }, + { url = "https://files.pythonhosted.org/packages/7a/1c/52d83a06e417cd9d4137722693424885cc9878249beb3a7c829e74bf7ce9/xxhash-3.6.0-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:bec91b562d8012dae276af8025a55811b875baace6af510412a5e58e3121bc54", size = 196409, upload-time = "2025-10-02T14:35:20.31Z" }, + { url = "https://files.pythonhosted.org/packages/e3/8e/c6d158d12a79bbd0b878f8355432075fc82759e356ab5a111463422a239b/xxhash-3.6.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:78e7f2f4c521c30ad5e786fdd6bae89d47a32672a80195467b5de0480aa97b1f", size = 215736, upload-time = "2025-10-02T14:35:21.616Z" }, + { url = "https://files.pythonhosted.org/packages/bc/68/c4c80614716345d55071a396cf03d06e34b5f4917a467faf43083c995155/xxhash-3.6.0-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:3ed0df1b11a79856df5ffcab572cbd6b9627034c1c748c5566fa79df9048a7c5", size = 214833, upload-time = "2025-10-02T14:35:23.32Z" }, + { url = "https://files.pythonhosted.org/packages/7e/e9/ae27c8ffec8b953efa84c7c4a6c6802c263d587b9fc0d6e7cea64e08c3af/xxhash-3.6.0-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0e4edbfc7d420925b0dd5e792478ed393d6e75ff8fc219a6546fb446b6a417b1", size = 448348, upload-time = "2025-10-02T14:35:25.111Z" }, + { url = "https://files.pythonhosted.org/packages/d7/6b/33e21afb1b5b3f46b74b6bd1913639066af218d704cc0941404ca717fc57/xxhash-3.6.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fba27a198363a7ef87f8c0f6b171ec36b674fe9053742c58dd7e3201c1ab30ee", size = 196070, upload-time = "2025-10-02T14:35:26.586Z" }, + { url = "https://files.pythonhosted.org/packages/96/b6/fcabd337bc5fa624e7203aa0fa7d0c49eed22f72e93229431752bddc83d9/xxhash-3.6.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:794fe9145fe60191c6532fa95063765529770edcdd67b3d537793e8004cabbfd", size = 212907, upload-time = "2025-10-02T14:35:28.087Z" }, + { url = "https://files.pythonhosted.org/packages/4b/d3/9ee6160e644d660fcf176c5825e61411c7f62648728f69c79ba237250143/xxhash-3.6.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:6105ef7e62b5ac73a837778efc331a591d8442f8ef5c7e102376506cb4ae2729", size = 200839, upload-time = "2025-10-02T14:35:29.857Z" }, + { url = "https://files.pythonhosted.org/packages/0d/98/e8de5baa5109394baf5118f5e72ab21a86387c4f89b0e77ef3e2f6b0327b/xxhash-3.6.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:f01375c0e55395b814a679b3eea205db7919ac2af213f4a6682e01220e5fe292", size = 213304, upload-time = "2025-10-02T14:35:31.222Z" }, + { url = "https://files.pythonhosted.org/packages/7b/1d/71056535dec5c3177eeb53e38e3d367dd1d16e024e63b1cee208d572a033/xxhash-3.6.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:d706dca2d24d834a4661619dcacf51a75c16d65985718d6a7d73c1eeeb903ddf", size = 416930, upload-time = "2025-10-02T14:35:32.517Z" }, + { url = "https://files.pythonhosted.org/packages/dc/6c/5cbde9de2cd967c322e651c65c543700b19e7ae3e0aae8ece3469bf9683d/xxhash-3.6.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:5f059d9faeacd49c0215d66f4056e1326c80503f51a1532ca336a385edadd033", size = 193787, upload-time = "2025-10-02T14:35:33.827Z" }, + { url = "https://files.pythonhosted.org/packages/19/fa/0172e350361d61febcea941b0cc541d6e6c8d65d153e85f850a7b256ff8a/xxhash-3.6.0-cp313-cp313t-win32.whl", hash = "sha256:1244460adc3a9be84731d72b8e80625788e5815b68da3da8b83f78115a40a7ec", size = 30916, upload-time = "2025-10-02T14:35:35.107Z" }, + { url = "https://files.pythonhosted.org/packages/ad/e6/e8cf858a2b19d6d45820f072eff1bea413910592ff17157cabc5f1227a16/xxhash-3.6.0-cp313-cp313t-win_amd64.whl", hash = "sha256:b1e420ef35c503869c4064f4a2f2b08ad6431ab7b229a05cce39d74268bca6b8", size = 31799, upload-time = "2025-10-02T14:35:36.165Z" }, + { url = "https://files.pythonhosted.org/packages/56/15/064b197e855bfb7b343210e82490ae672f8bc7cdf3ddb02e92f64304ee8a/xxhash-3.6.0-cp313-cp313t-win_arm64.whl", hash = "sha256:ec44b73a4220623235f67a996c862049f375df3b1052d9899f40a6382c32d746", size = 28044, upload-time = "2025-10-02T14:35:37.195Z" }, + { url = "https://files.pythonhosted.org/packages/7e/5e/0138bc4484ea9b897864d59fce9be9086030825bc778b76cb5a33a906d37/xxhash-3.6.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:a40a3d35b204b7cc7643cbcf8c9976d818cb47befcfac8bbefec8038ac363f3e", size = 32754, upload-time = "2025-10-02T14:35:38.245Z" }, + { url = "https://files.pythonhosted.org/packages/18/d7/5dac2eb2ec75fd771957a13e5dda560efb2176d5203f39502a5fc571f899/xxhash-3.6.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:a54844be970d3fc22630b32d515e79a90d0a3ddb2644d8d7402e3c4c8da61405", size = 30846, upload-time = "2025-10-02T14:35:39.6Z" }, + { url = "https://files.pythonhosted.org/packages/fe/71/8bc5be2bb00deb5682e92e8da955ebe5fa982da13a69da5a40a4c8db12fb/xxhash-3.6.0-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:016e9190af8f0a4e3741343777710e3d5717427f175adfdc3e72508f59e2a7f3", size = 194343, upload-time = "2025-10-02T14:35:40.69Z" }, + { url = "https://files.pythonhosted.org/packages/e7/3b/52badfb2aecec2c377ddf1ae75f55db3ba2d321c5e164f14461c90837ef3/xxhash-3.6.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4f6f72232f849eb9d0141e2ebe2677ece15adfd0fa599bc058aad83c714bb2c6", size = 213074, upload-time = "2025-10-02T14:35:42.29Z" }, + { url = "https://files.pythonhosted.org/packages/a2/2b/ae46b4e9b92e537fa30d03dbc19cdae57ed407e9c26d163895e968e3de85/xxhash-3.6.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:63275a8aba7865e44b1813d2177e0f5ea7eadad3dd063a21f7cf9afdc7054063", size = 212388, upload-time = "2025-10-02T14:35:43.929Z" }, + { url = "https://files.pythonhosted.org/packages/f5/80/49f88d3afc724b4ac7fbd664c8452d6db51b49915be48c6982659e0e7942/xxhash-3.6.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3cd01fa2aa00d8b017c97eb46b9a794fbdca53fc14f845f5a328c71254b0abb7", size = 445614, upload-time = "2025-10-02T14:35:45.216Z" }, + { url = "https://files.pythonhosted.org/packages/ed/ba/603ce3961e339413543d8cd44f21f2c80e2a7c5cfe692a7b1f2cccf58f3c/xxhash-3.6.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0226aa89035b62b6a86d3c68df4d7c1f47a342b8683da2b60cedcddb46c4d95b", size = 194024, upload-time = "2025-10-02T14:35:46.959Z" }, + { url = "https://files.pythonhosted.org/packages/78/d1/8e225ff7113bf81545cfdcd79eef124a7b7064a0bba53605ff39590b95c2/xxhash-3.6.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c6e193e9f56e4ca4923c61238cdaced324f0feac782544eb4c6d55ad5cc99ddd", size = 210541, upload-time = "2025-10-02T14:35:48.301Z" }, + { url = "https://files.pythonhosted.org/packages/6f/58/0f89d149f0bad89def1a8dd38feb50ccdeb643d9797ec84707091d4cb494/xxhash-3.6.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:9176dcaddf4ca963d4deb93866d739a343c01c969231dbe21680e13a5d1a5bf0", size = 198305, upload-time = "2025-10-02T14:35:49.584Z" }, + { url = "https://files.pythonhosted.org/packages/11/38/5eab81580703c4df93feb5f32ff8fa7fe1e2c51c1f183ee4e48d4bb9d3d7/xxhash-3.6.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:c1ce4009c97a752e682b897aa99aef84191077a9433eb237774689f14f8ec152", size = 210848, upload-time = "2025-10-02T14:35:50.877Z" }, + { url = "https://files.pythonhosted.org/packages/5e/6b/953dc4b05c3ce678abca756416e4c130d2382f877a9c30a20d08ee6a77c0/xxhash-3.6.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:8cb2f4f679b01513b7adbb9b1b2f0f9cdc31b70007eaf9d59d0878809f385b11", size = 414142, upload-time = "2025-10-02T14:35:52.15Z" }, + { url = "https://files.pythonhosted.org/packages/08/a9/238ec0d4e81a10eb5026d4a6972677cbc898ba6c8b9dbaec12ae001b1b35/xxhash-3.6.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:653a91d7c2ab54a92c19ccf43508b6a555440b9be1bc8be553376778be7f20b5", size = 191547, upload-time = "2025-10-02T14:35:53.547Z" }, + { url = "https://files.pythonhosted.org/packages/f1/ee/3cf8589e06c2164ac77c3bf0aa127012801128f1feebf2a079272da5737c/xxhash-3.6.0-cp314-cp314-win32.whl", hash = "sha256:a756fe893389483ee8c394d06b5ab765d96e68fbbfe6fde7aa17e11f5720559f", size = 31214, upload-time = "2025-10-02T14:35:54.746Z" }, + { url = "https://files.pythonhosted.org/packages/02/5d/a19552fbc6ad4cb54ff953c3908bbc095f4a921bc569433d791f755186f1/xxhash-3.6.0-cp314-cp314-win_amd64.whl", hash = "sha256:39be8e4e142550ef69629c9cd71b88c90e9a5db703fecbcf265546d9536ca4ad", size = 32290, upload-time = "2025-10-02T14:35:55.791Z" }, + { url = "https://files.pythonhosted.org/packages/b1/11/dafa0643bc30442c887b55baf8e73353a344ee89c1901b5a5c54a6c17d39/xxhash-3.6.0-cp314-cp314-win_arm64.whl", hash = "sha256:25915e6000338999236f1eb68a02a32c3275ac338628a7eaa5a269c401995679", size = 28795, upload-time = "2025-10-02T14:35:57.162Z" }, + { url = "https://files.pythonhosted.org/packages/2c/db/0e99732ed7f64182aef4a6fb145e1a295558deec2a746265dcdec12d191e/xxhash-3.6.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:c5294f596a9017ca5a3e3f8884c00b91ab2ad2933cf288f4923c3fd4346cf3d4", size = 32955, upload-time = "2025-10-02T14:35:58.267Z" }, + { url = "https://files.pythonhosted.org/packages/55/f4/2a7c3c68e564a099becfa44bb3d398810cc0ff6749b0d3cb8ccb93f23c14/xxhash-3.6.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1cf9dcc4ab9cff01dfbba78544297a3a01dafd60f3bde4e2bfd016cf7e4ddc67", size = 31072, upload-time = "2025-10-02T14:35:59.382Z" }, + { url = "https://files.pythonhosted.org/packages/c6/d9/72a29cddc7250e8a5819dad5d466facb5dc4c802ce120645630149127e73/xxhash-3.6.0-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:01262da8798422d0685f7cef03b2bd3f4f46511b02830861df548d7def4402ad", size = 196579, upload-time = "2025-10-02T14:36:00.838Z" }, + { url = "https://files.pythonhosted.org/packages/63/93/b21590e1e381040e2ca305a884d89e1c345b347404f7780f07f2cdd47ef4/xxhash-3.6.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:51a73fb7cb3a3ead9f7a8b583ffd9b8038e277cdb8cb87cf890e88b3456afa0b", size = 215854, upload-time = "2025-10-02T14:36:02.207Z" }, + { url = "https://files.pythonhosted.org/packages/ce/b8/edab8a7d4fa14e924b29be877d54155dcbd8b80be85ea00d2be3413a9ed4/xxhash-3.6.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b9c6df83594f7df8f7f708ce5ebeacfc69f72c9fbaaababf6cf4758eaada0c9b", size = 214965, upload-time = "2025-10-02T14:36:03.507Z" }, + { url = "https://files.pythonhosted.org/packages/27/67/dfa980ac7f0d509d54ea0d5a486d2bb4b80c3f1bb22b66e6a05d3efaf6c0/xxhash-3.6.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:627f0af069b0ea56f312fd5189001c24578868643203bca1abbc2c52d3a6f3ca", size = 448484, upload-time = "2025-10-02T14:36:04.828Z" }, + { url = "https://files.pythonhosted.org/packages/8c/63/8ffc2cc97e811c0ca5d00ab36604b3ea6f4254f20b7bc658ca825ce6c954/xxhash-3.6.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:aa912c62f842dfd013c5f21a642c9c10cd9f4c4e943e0af83618b4a404d9091a", size = 196162, upload-time = "2025-10-02T14:36:06.182Z" }, + { url = "https://files.pythonhosted.org/packages/4b/77/07f0e7a3edd11a6097e990f6e5b815b6592459cb16dae990d967693e6ea9/xxhash-3.6.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:b465afd7909db30168ab62afe40b2fcf79eedc0b89a6c0ab3123515dc0df8b99", size = 213007, upload-time = "2025-10-02T14:36:07.733Z" }, + { url = "https://files.pythonhosted.org/packages/ae/d8/bc5fa0d152837117eb0bef6f83f956c509332ce133c91c63ce07ee7c4873/xxhash-3.6.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:a881851cf38b0a70e7c4d3ce81fc7afd86fbc2a024f4cfb2a97cf49ce04b75d3", size = 200956, upload-time = "2025-10-02T14:36:09.106Z" }, + { url = "https://files.pythonhosted.org/packages/26/a5/d749334130de9411783873e9b98ecc46688dad5db64ca6e04b02acc8b473/xxhash-3.6.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:9b3222c686a919a0f3253cfc12bb118b8b103506612253b5baeaac10d8027cf6", size = 213401, upload-time = "2025-10-02T14:36:10.585Z" }, + { url = "https://files.pythonhosted.org/packages/89/72/abed959c956a4bfc72b58c0384bb7940663c678127538634d896b1195c10/xxhash-3.6.0-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:c5aa639bc113e9286137cec8fadc20e9cd732b2cc385c0b7fa673b84fc1f2a93", size = 417083, upload-time = "2025-10-02T14:36:12.276Z" }, + { url = "https://files.pythonhosted.org/packages/0c/b3/62fd2b586283b7d7d665fb98e266decadf31f058f1cf6c478741f68af0cb/xxhash-3.6.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5c1343d49ac102799905e115aee590183c3921d475356cb24b4de29a4bc56518", size = 193913, upload-time = "2025-10-02T14:36:14.025Z" }, + { url = "https://files.pythonhosted.org/packages/9a/9a/c19c42c5b3f5a4aad748a6d5b4f23df3bed7ee5445accc65a0fb3ff03953/xxhash-3.6.0-cp314-cp314t-win32.whl", hash = "sha256:5851f033c3030dd95c086b4a36a2683c2ff4a799b23af60977188b057e467119", size = 31586, upload-time = "2025-10-02T14:36:15.603Z" }, + { url = "https://files.pythonhosted.org/packages/03/d6/4cc450345be9924fd5dc8c590ceda1db5b43a0a889587b0ae81a95511360/xxhash-3.6.0-cp314-cp314t-win_amd64.whl", hash = "sha256:0444e7967dac37569052d2409b00a8860c2135cff05502df4da80267d384849f", size = 32526, upload-time = "2025-10-02T14:36:16.708Z" }, + { url = "https://files.pythonhosted.org/packages/0f/c9/7243eb3f9eaabd1a88a5a5acadf06df2d83b100c62684b7425c6a11bcaa8/xxhash-3.6.0-cp314-cp314t-win_arm64.whl", hash = "sha256:bb79b1e63f6fd84ec778a4b1916dfe0a7c3fdb986c06addd5db3a0d413819d95", size = 28898, upload-time = "2025-10-02T14:36:17.843Z" }, +] + +[[package]] +name = "yarl" +version = "1.22.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "idna" }, + { name = "multidict" }, + { name = "propcache" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/57/63/0c6ebca57330cd313f6102b16dd57ffaf3ec4c83403dcb45dbd15c6f3ea1/yarl-1.22.0.tar.gz", hash = "sha256:bebf8557577d4401ba8bd9ff33906f1376c877aa78d1fe216ad01b4d6745af71", size = 187169, upload-time = "2025-10-06T14:12:55.963Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ea/f3/d67de7260456ee105dc1d162d43a019ecad6b91e2f51809d6cddaa56690e/yarl-1.22.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:8dee9c25c74997f6a750cd317b8ca63545169c098faee42c84aa5e506c819b53", size = 139980, upload-time = "2025-10-06T14:10:14.601Z" }, + { url = "https://files.pythonhosted.org/packages/01/88/04d98af0b47e0ef42597b9b28863b9060bb515524da0a65d5f4db160b2d5/yarl-1.22.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:01e73b85a5434f89fc4fe27dcda2aff08ddf35e4d47bbbea3bdcd25321af538a", size = 93424, upload-time = "2025-10-06T14:10:16.115Z" }, + { url = "https://files.pythonhosted.org/packages/18/91/3274b215fd8442a03975ce6bee5fe6aa57a8326b29b9d3d56234a1dca244/yarl-1.22.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:22965c2af250d20c873cdbee8ff958fb809940aeb2e74ba5f20aaf6b7ac8c70c", size = 93821, upload-time = "2025-10-06T14:10:17.993Z" }, + { url = "https://files.pythonhosted.org/packages/61/3a/caf4e25036db0f2da4ca22a353dfeb3c9d3c95d2761ebe9b14df8fc16eb0/yarl-1.22.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b4f15793aa49793ec8d1c708ab7f9eded1aa72edc5174cae703651555ed1b601", size = 373243, upload-time = "2025-10-06T14:10:19.44Z" }, + { url = "https://files.pythonhosted.org/packages/6e/9e/51a77ac7516e8e7803b06e01f74e78649c24ee1021eca3d6a739cb6ea49c/yarl-1.22.0-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:e5542339dcf2747135c5c85f68680353d5cb9ffd741c0f2e8d832d054d41f35a", size = 342361, upload-time = "2025-10-06T14:10:21.124Z" }, + { url = "https://files.pythonhosted.org/packages/d4/f8/33b92454789dde8407f156c00303e9a891f1f51a0330b0fad7c909f87692/yarl-1.22.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:5c401e05ad47a75869c3ab3e35137f8468b846770587e70d71e11de797d113df", size = 387036, upload-time = "2025-10-06T14:10:22.902Z" }, + { url = "https://files.pythonhosted.org/packages/d9/9a/c5db84ea024f76838220280f732970aa4ee154015d7f5c1bfb60a267af6f/yarl-1.22.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:243dda95d901c733f5b59214d28b0120893d91777cb8aa043e6ef059d3cddfe2", size = 397671, upload-time = "2025-10-06T14:10:24.523Z" }, + { url = "https://files.pythonhosted.org/packages/11/c9/cd8538dc2e7727095e0c1d867bad1e40c98f37763e6d995c1939f5fdc7b1/yarl-1.22.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bec03d0d388060058f5d291a813f21c011041938a441c593374da6077fe21b1b", size = 377059, upload-time = "2025-10-06T14:10:26.406Z" }, + { url = "https://files.pythonhosted.org/packages/a1/b9/ab437b261702ced75122ed78a876a6dec0a1b0f5e17a4ac7a9a2482d8abe/yarl-1.22.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:b0748275abb8c1e1e09301ee3cf90c8a99678a4e92e4373705f2a2570d581273", size = 365356, upload-time = "2025-10-06T14:10:28.461Z" }, + { url = "https://files.pythonhosted.org/packages/b2/9d/8e1ae6d1d008a9567877b08f0ce4077a29974c04c062dabdb923ed98e6fe/yarl-1.22.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:47fdb18187e2a4e18fda2c25c05d8251a9e4a521edaed757fef033e7d8498d9a", size = 361331, upload-time = "2025-10-06T14:10:30.541Z" }, + { url = "https://files.pythonhosted.org/packages/ca/5a/09b7be3905962f145b73beb468cdd53db8aa171cf18c80400a54c5b82846/yarl-1.22.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:c7044802eec4524fde550afc28edda0dd5784c4c45f0be151a2d3ba017daca7d", size = 382590, upload-time = "2025-10-06T14:10:33.352Z" }, + { url = "https://files.pythonhosted.org/packages/aa/7f/59ec509abf90eda5048b0bc3e2d7b5099dffdb3e6b127019895ab9d5ef44/yarl-1.22.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:139718f35149ff544caba20fce6e8a2f71f1e39b92c700d8438a0b1d2a631a02", size = 385316, upload-time = "2025-10-06T14:10:35.034Z" }, + { url = "https://files.pythonhosted.org/packages/e5/84/891158426bc8036bfdfd862fabd0e0fa25df4176ec793e447f4b85cf1be4/yarl-1.22.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e1b51bebd221006d3d2f95fbe124b22b247136647ae5dcc8c7acafba66e5ee67", size = 374431, upload-time = "2025-10-06T14:10:37.76Z" }, + { url = "https://files.pythonhosted.org/packages/bb/49/03da1580665baa8bef5e8ed34c6df2c2aca0a2f28bf397ed238cc1bbc6f2/yarl-1.22.0-cp313-cp313-win32.whl", hash = "sha256:d3e32536234a95f513bd374e93d717cf6b2231a791758de6c509e3653f234c95", size = 81555, upload-time = "2025-10-06T14:10:39.649Z" }, + { url = "https://files.pythonhosted.org/packages/9a/ee/450914ae11b419eadd067c6183ae08381cfdfcb9798b90b2b713bbebddda/yarl-1.22.0-cp313-cp313-win_amd64.whl", hash = "sha256:47743b82b76d89a1d20b83e60d5c20314cbd5ba2befc9cda8f28300c4a08ed4d", size = 86965, upload-time = "2025-10-06T14:10:41.313Z" }, + { url = "https://files.pythonhosted.org/packages/98/4d/264a01eae03b6cf629ad69bae94e3b0e5344741e929073678e84bf7a3e3b/yarl-1.22.0-cp313-cp313-win_arm64.whl", hash = "sha256:5d0fcda9608875f7d052eff120c7a5da474a6796fe4d83e152e0e4d42f6d1a9b", size = 81205, upload-time = "2025-10-06T14:10:43.167Z" }, + { url = "https://files.pythonhosted.org/packages/88/fc/6908f062a2f77b5f9f6d69cecb1747260831ff206adcbc5b510aff88df91/yarl-1.22.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:719ae08b6972befcba4310e49edb1161a88cdd331e3a694b84466bd938a6ab10", size = 146209, upload-time = "2025-10-06T14:10:44.643Z" }, + { url = "https://files.pythonhosted.org/packages/65/47/76594ae8eab26210b4867be6f49129861ad33da1f1ebdf7051e98492bf62/yarl-1.22.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:47d8a5c446df1c4db9d21b49619ffdba90e77c89ec6e283f453856c74b50b9e3", size = 95966, upload-time = "2025-10-06T14:10:46.554Z" }, + { url = "https://files.pythonhosted.org/packages/ab/ce/05e9828a49271ba6b5b038b15b3934e996980dd78abdfeb52a04cfb9467e/yarl-1.22.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:cfebc0ac8333520d2d0423cbbe43ae43c8838862ddb898f5ca68565e395516e9", size = 97312, upload-time = "2025-10-06T14:10:48.007Z" }, + { url = "https://files.pythonhosted.org/packages/d1/c5/7dffad5e4f2265b29c9d7ec869c369e4223166e4f9206fc2243ee9eea727/yarl-1.22.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4398557cbf484207df000309235979c79c4356518fd5c99158c7d38203c4da4f", size = 361967, upload-time = "2025-10-06T14:10:49.997Z" }, + { url = "https://files.pythonhosted.org/packages/50/b2/375b933c93a54bff7fc041e1a6ad2c0f6f733ffb0c6e642ce56ee3b39970/yarl-1.22.0-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:2ca6fd72a8cd803be290d42f2dec5cdcd5299eeb93c2d929bf060ad9efaf5de0", size = 323949, upload-time = "2025-10-06T14:10:52.004Z" }, + { url = "https://files.pythonhosted.org/packages/66/50/bfc2a29a1d78644c5a7220ce2f304f38248dc94124a326794e677634b6cf/yarl-1.22.0-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ca1f59c4e1ab6e72f0a23c13fca5430f889634166be85dbf1013683e49e3278e", size = 361818, upload-time = "2025-10-06T14:10:54.078Z" }, + { url = "https://files.pythonhosted.org/packages/46/96/f3941a46af7d5d0f0498f86d71275696800ddcdd20426298e572b19b91ff/yarl-1.22.0-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:6c5010a52015e7c70f86eb967db0f37f3c8bd503a695a49f8d45700144667708", size = 372626, upload-time = "2025-10-06T14:10:55.767Z" }, + { url = "https://files.pythonhosted.org/packages/c1/42/8b27c83bb875cd89448e42cd627e0fb971fa1675c9ec546393d18826cb50/yarl-1.22.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9d7672ecf7557476642c88497c2f8d8542f8e36596e928e9bcba0e42e1e7d71f", size = 341129, upload-time = "2025-10-06T14:10:57.985Z" }, + { url = "https://files.pythonhosted.org/packages/49/36/99ca3122201b382a3cf7cc937b95235b0ac944f7e9f2d5331d50821ed352/yarl-1.22.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:3b7c88eeef021579d600e50363e0b6ee4f7f6f728cd3486b9d0f3ee7b946398d", size = 346776, upload-time = "2025-10-06T14:10:59.633Z" }, + { url = "https://files.pythonhosted.org/packages/85/b4/47328bf996acd01a4c16ef9dcd2f59c969f495073616586f78cd5f2efb99/yarl-1.22.0-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:f4afb5c34f2c6fecdcc182dfcfc6af6cccf1aa923eed4d6a12e9d96904e1a0d8", size = 334879, upload-time = "2025-10-06T14:11:01.454Z" }, + { url = "https://files.pythonhosted.org/packages/c2/ad/b77d7b3f14a4283bffb8e92c6026496f6de49751c2f97d4352242bba3990/yarl-1.22.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:59c189e3e99a59cf8d83cbb31d4db02d66cda5a1a4374e8a012b51255341abf5", size = 350996, upload-time = "2025-10-06T14:11:03.452Z" }, + { url = "https://files.pythonhosted.org/packages/81/c8/06e1d69295792ba54d556f06686cbd6a7ce39c22307100e3fb4a2c0b0a1d/yarl-1.22.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:5a3bf7f62a289fa90f1990422dc8dff5a458469ea71d1624585ec3a4c8d6960f", size = 356047, upload-time = "2025-10-06T14:11:05.115Z" }, + { url = "https://files.pythonhosted.org/packages/4b/b8/4c0e9e9f597074b208d18cef227d83aac36184bfbc6eab204ea55783dbc5/yarl-1.22.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:de6b9a04c606978fdfe72666fa216ffcf2d1a9f6a381058d4378f8d7b1e5de62", size = 342947, upload-time = "2025-10-06T14:11:08.137Z" }, + { url = "https://files.pythonhosted.org/packages/e0/e5/11f140a58bf4c6ad7aca69a892bff0ee638c31bea4206748fc0df4ebcb3a/yarl-1.22.0-cp313-cp313t-win32.whl", hash = "sha256:1834bb90991cc2999f10f97f5f01317f99b143284766d197e43cd5b45eb18d03", size = 86943, upload-time = "2025-10-06T14:11:10.284Z" }, + { url = "https://files.pythonhosted.org/packages/31/74/8b74bae38ed7fe6793d0c15a0c8207bbb819cf287788459e5ed230996cdd/yarl-1.22.0-cp313-cp313t-win_amd64.whl", hash = "sha256:ff86011bd159a9d2dfc89c34cfd8aff12875980e3bd6a39ff097887520e60249", size = 93715, upload-time = "2025-10-06T14:11:11.739Z" }, + { url = "https://files.pythonhosted.org/packages/69/66/991858aa4b5892d57aef7ee1ba6b4d01ec3b7eb3060795d34090a3ca3278/yarl-1.22.0-cp313-cp313t-win_arm64.whl", hash = "sha256:7861058d0582b847bc4e3a4a4c46828a410bca738673f35a29ba3ca5db0b473b", size = 83857, upload-time = "2025-10-06T14:11:13.586Z" }, + { url = "https://files.pythonhosted.org/packages/46/b3/e20ef504049f1a1c54a814b4b9bed96d1ac0e0610c3b4da178f87209db05/yarl-1.22.0-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:34b36c2c57124530884d89d50ed2c1478697ad7473efd59cfd479945c95650e4", size = 140520, upload-time = "2025-10-06T14:11:15.465Z" }, + { url = "https://files.pythonhosted.org/packages/e4/04/3532d990fdbab02e5ede063676b5c4260e7f3abea2151099c2aa745acc4c/yarl-1.22.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:0dd9a702591ca2e543631c2a017e4a547e38a5c0f29eece37d9097e04a7ac683", size = 93504, upload-time = "2025-10-06T14:11:17.106Z" }, + { url = "https://files.pythonhosted.org/packages/11/63/ff458113c5c2dac9a9719ac68ee7c947cb621432bcf28c9972b1c0e83938/yarl-1.22.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:594fcab1032e2d2cc3321bb2e51271e7cd2b516c7d9aee780ece81b07ff8244b", size = 94282, upload-time = "2025-10-06T14:11:19.064Z" }, + { url = "https://files.pythonhosted.org/packages/a7/bc/315a56aca762d44a6aaaf7ad253f04d996cb6b27bad34410f82d76ea8038/yarl-1.22.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f3d7a87a78d46a2e3d5b72587ac14b4c16952dd0887dbb051451eceac774411e", size = 372080, upload-time = "2025-10-06T14:11:20.996Z" }, + { url = "https://files.pythonhosted.org/packages/3f/3f/08e9b826ec2e099ea6e7c69a61272f4f6da62cb5b1b63590bb80ca2e4a40/yarl-1.22.0-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:852863707010316c973162e703bddabec35e8757e67fcb8ad58829de1ebc8590", size = 338696, upload-time = "2025-10-06T14:11:22.847Z" }, + { url = "https://files.pythonhosted.org/packages/e3/9f/90360108e3b32bd76789088e99538febfea24a102380ae73827f62073543/yarl-1.22.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:131a085a53bfe839a477c0845acf21efc77457ba2bcf5899618136d64f3303a2", size = 387121, upload-time = "2025-10-06T14:11:24.889Z" }, + { url = "https://files.pythonhosted.org/packages/98/92/ab8d4657bd5b46a38094cfaea498f18bb70ce6b63508fd7e909bd1f93066/yarl-1.22.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:078a8aefd263f4d4f923a9677b942b445a2be970ca24548a8102689a3a8ab8da", size = 394080, upload-time = "2025-10-06T14:11:27.307Z" }, + { url = "https://files.pythonhosted.org/packages/f5/e7/d8c5a7752fef68205296201f8ec2bf718f5c805a7a7e9880576c67600658/yarl-1.22.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bca03b91c323036913993ff5c738d0842fc9c60c4648e5c8d98331526df89784", size = 372661, upload-time = "2025-10-06T14:11:29.387Z" }, + { url = "https://files.pythonhosted.org/packages/b6/2e/f4d26183c8db0bb82d491b072f3127fb8c381a6206a3a56332714b79b751/yarl-1.22.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:68986a61557d37bb90d3051a45b91fa3d5c516d177dfc6dd6f2f436a07ff2b6b", size = 364645, upload-time = "2025-10-06T14:11:31.423Z" }, + { url = "https://files.pythonhosted.org/packages/80/7c/428e5812e6b87cd00ee8e898328a62c95825bf37c7fa87f0b6bb2ad31304/yarl-1.22.0-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:4792b262d585ff0dff6bcb787f8492e40698443ec982a3568c2096433660c694", size = 355361, upload-time = "2025-10-06T14:11:33.055Z" }, + { url = "https://files.pythonhosted.org/packages/ec/2a/249405fd26776f8b13c067378ef4d7dd49c9098d1b6457cdd152a99e96a9/yarl-1.22.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:ebd4549b108d732dba1d4ace67614b9545b21ece30937a63a65dd34efa19732d", size = 381451, upload-time = "2025-10-06T14:11:35.136Z" }, + { url = "https://files.pythonhosted.org/packages/67/a8/fb6b1adbe98cf1e2dd9fad71003d3a63a1bc22459c6e15f5714eb9323b93/yarl-1.22.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:f87ac53513d22240c7d59203f25cc3beac1e574c6cd681bbfd321987b69f95fd", size = 383814, upload-time = "2025-10-06T14:11:37.094Z" }, + { url = "https://files.pythonhosted.org/packages/d9/f9/3aa2c0e480fb73e872ae2814c43bc1e734740bb0d54e8cb2a95925f98131/yarl-1.22.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:22b029f2881599e2f1b06f8f1db2ee63bd309e2293ba2d566e008ba12778b8da", size = 370799, upload-time = "2025-10-06T14:11:38.83Z" }, + { url = "https://files.pythonhosted.org/packages/50/3c/af9dba3b8b5eeb302f36f16f92791f3ea62e3f47763406abf6d5a4a3333b/yarl-1.22.0-cp314-cp314-win32.whl", hash = "sha256:6a635ea45ba4ea8238463b4f7d0e721bad669f80878b7bfd1f89266e2ae63da2", size = 82990, upload-time = "2025-10-06T14:11:40.624Z" }, + { url = "https://files.pythonhosted.org/packages/ac/30/ac3a0c5bdc1d6efd1b41fa24d4897a4329b3b1e98de9449679dd327af4f0/yarl-1.22.0-cp314-cp314-win_amd64.whl", hash = "sha256:0d6e6885777af0f110b0e5d7e5dda8b704efed3894da26220b7f3d887b839a79", size = 88292, upload-time = "2025-10-06T14:11:42.578Z" }, + { url = "https://files.pythonhosted.org/packages/df/0a/227ab4ff5b998a1b7410abc7b46c9b7a26b0ca9e86c34ba4b8d8bc7c63d5/yarl-1.22.0-cp314-cp314-win_arm64.whl", hash = "sha256:8218f4e98d3c10d683584cb40f0424f4b9fd6e95610232dd75e13743b070ee33", size = 82888, upload-time = "2025-10-06T14:11:44.863Z" }, + { url = "https://files.pythonhosted.org/packages/06/5e/a15eb13db90abd87dfbefb9760c0f3f257ac42a5cac7e75dbc23bed97a9f/yarl-1.22.0-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:45c2842ff0e0d1b35a6bf1cd6c690939dacb617a70827f715232b2e0494d55d1", size = 146223, upload-time = "2025-10-06T14:11:46.796Z" }, + { url = "https://files.pythonhosted.org/packages/18/82/9665c61910d4d84f41a5bf6837597c89e665fa88aa4941080704645932a9/yarl-1.22.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:d947071e6ebcf2e2bee8fce76e10faca8f7a14808ca36a910263acaacef08eca", size = 95981, upload-time = "2025-10-06T14:11:48.845Z" }, + { url = "https://files.pythonhosted.org/packages/5d/9a/2f65743589809af4d0a6d3aa749343c4b5f4c380cc24a8e94a3c6625a808/yarl-1.22.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:334b8721303e61b00019474cc103bdac3d7b1f65e91f0bfedeec2d56dfe74b53", size = 97303, upload-time = "2025-10-06T14:11:50.897Z" }, + { url = "https://files.pythonhosted.org/packages/b0/ab/5b13d3e157505c43c3b43b5a776cbf7b24a02bc4cccc40314771197e3508/yarl-1.22.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1e7ce67c34138a058fd092f67d07a72b8e31ff0c9236e751957465a24b28910c", size = 361820, upload-time = "2025-10-06T14:11:52.549Z" }, + { url = "https://files.pythonhosted.org/packages/fb/76/242a5ef4677615cf95330cfc1b4610e78184400699bdda0acb897ef5e49a/yarl-1.22.0-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:d77e1b2c6d04711478cb1c4ab90db07f1609ccf06a287d5607fcd90dc9863acf", size = 323203, upload-time = "2025-10-06T14:11:54.225Z" }, + { url = "https://files.pythonhosted.org/packages/8c/96/475509110d3f0153b43d06164cf4195c64d16999e0c7e2d8a099adcd6907/yarl-1.22.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c4647674b6150d2cae088fc07de2738a84b8bcedebef29802cf0b0a82ab6face", size = 363173, upload-time = "2025-10-06T14:11:56.069Z" }, + { url = "https://files.pythonhosted.org/packages/c9/66/59db471aecfbd559a1fd48aedd954435558cd98c7d0da8b03cc6c140a32c/yarl-1.22.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:efb07073be061c8f79d03d04139a80ba33cbd390ca8f0297aae9cce6411e4c6b", size = 373562, upload-time = "2025-10-06T14:11:58.783Z" }, + { url = "https://files.pythonhosted.org/packages/03/1f/c5d94abc91557384719da10ff166b916107c1b45e4d0423a88457071dd88/yarl-1.22.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e51ac5435758ba97ad69617e13233da53908beccc6cfcd6c34bbed8dcbede486", size = 339828, upload-time = "2025-10-06T14:12:00.686Z" }, + { url = "https://files.pythonhosted.org/packages/5f/97/aa6a143d3afba17b6465733681c70cf175af89f76ec8d9286e08437a7454/yarl-1.22.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:33e32a0dd0c8205efa8e83d04fc9f19313772b78522d1bdc7d9aed706bfd6138", size = 347551, upload-time = "2025-10-06T14:12:02.628Z" }, + { url = "https://files.pythonhosted.org/packages/43/3c/45a2b6d80195959239a7b2a8810506d4eea5487dce61c2a3393e7fc3c52e/yarl-1.22.0-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:bf4a21e58b9cde0e401e683ebd00f6ed30a06d14e93f7c8fd059f8b6e8f87b6a", size = 334512, upload-time = "2025-10-06T14:12:04.871Z" }, + { url = "https://files.pythonhosted.org/packages/86/a0/c2ab48d74599c7c84cb104ebd799c5813de252bea0f360ffc29d270c2caa/yarl-1.22.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:e4b582bab49ac33c8deb97e058cd67c2c50dac0dd134874106d9c774fd272529", size = 352400, upload-time = "2025-10-06T14:12:06.624Z" }, + { url = "https://files.pythonhosted.org/packages/32/75/f8919b2eafc929567d3d8411f72bdb1a2109c01caaab4ebfa5f8ffadc15b/yarl-1.22.0-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:0b5bcc1a9c4839e7e30b7b30dd47fe5e7e44fb7054ec29b5bb8d526aa1041093", size = 357140, upload-time = "2025-10-06T14:12:08.362Z" }, + { url = "https://files.pythonhosted.org/packages/cf/72/6a85bba382f22cf78add705d8c3731748397d986e197e53ecc7835e76de7/yarl-1.22.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:c0232bce2170103ec23c454e54a57008a9a72b5d1c3105dc2496750da8cfa47c", size = 341473, upload-time = "2025-10-06T14:12:10.994Z" }, + { url = "https://files.pythonhosted.org/packages/35/18/55e6011f7c044dc80b98893060773cefcfdbf60dfefb8cb2f58b9bacbd83/yarl-1.22.0-cp314-cp314t-win32.whl", hash = "sha256:8009b3173bcd637be650922ac455946197d858b3630b6d8787aa9e5c4564533e", size = 89056, upload-time = "2025-10-06T14:12:13.317Z" }, + { url = "https://files.pythonhosted.org/packages/f9/86/0f0dccb6e59a9e7f122c5afd43568b1d31b8ab7dda5f1b01fb5c7025c9a9/yarl-1.22.0-cp314-cp314t-win_amd64.whl", hash = "sha256:9fb17ea16e972c63d25d4a97f016d235c78dd2344820eb35bc034bc32012ee27", size = 96292, upload-time = "2025-10-06T14:12:15.398Z" }, + { url = "https://files.pythonhosted.org/packages/48/b7/503c98092fb3b344a179579f55814b613c1fbb1c23b3ec14a7b008a66a6e/yarl-1.22.0-cp314-cp314t-win_arm64.whl", hash = "sha256:9f6d73c1436b934e3f01df1e1b21ff765cd1d28c77dfb9ace207f746d4610ee1", size = 85171, upload-time = "2025-10-06T14:12:16.935Z" }, + { url = "https://files.pythonhosted.org/packages/73/ae/b48f95715333080afb75a4504487cbe142cae1268afc482d06692d605ae6/yarl-1.22.0-py3-none-any.whl", hash = "sha256:1380560bdba02b6b6c90de54133c81c9f2a453dee9912fe58c1dcced1edb7cff", size = 46814, upload-time = "2025-10-06T14:12:53.872Z" }, +] From 9a91325550993cb1fe70247445c4f7dab2a7024d Mon Sep 17 00:00:00 2001 From: ramyamounir <44840015+ramyamounir@users.noreply.github.com> Date: Tue, 2 Dec 2025 07:27:02 -0500 Subject: [PATCH 02/15] refactor!: alpha to mesh transparency --- ...interactive_hypothesis_space_pointcloud.py | 35 ++++++++++--------- 1 file changed, 19 insertions(+), 16 deletions(-) diff --git a/src/tbp/plot/plots/interactive_hypothesis_space_pointcloud.py b/src/tbp/plot/plots/interactive_hypothesis_space_pointcloud.py index c52054e..cfb46ae 100644 --- a/src/tbp/plot/plots/interactive_hypothesis_space_pointcloud.py +++ b/src/tbp/plot/plots/interactive_hypothesis_space_pointcloud.py @@ -272,7 +272,7 @@ def __init__( WidgetUpdater( topics=[ TopicSpec("episode_number", required=True), - TopicSpec("alpha_value", required=True), + TopicSpec("transparency_value", required=True), ], callback=self.update_mesh, ), @@ -301,9 +301,9 @@ def __init__( ), WidgetUpdater( topics=[ - TopicSpec("alpha_value", required=True), + TopicSpec("transparency_value", required=True), ], - callback=self.update_alpha, + callback=self.update_transparency, ), ] self._locators = self.create_locators() @@ -395,7 +395,7 @@ def update_mesh( widget.rotate_y(target_rot[1]) widget.rotate_z(target_rot[2]) widget.shift(*target_pos) - widget.alpha(msgs_dict["alpha_value"]) + widget.alpha(1.0 - msgs_dict["transparency_value"]) self.plotter.at(1).add(widget) self.plotter.at(1).render() @@ -552,18 +552,21 @@ def update_patch_path( self.plotter.at(1).render() return widget, False - def update_alpha(self, widget: None, msgs: list[TopicMessage]) -> tuple[None, bool]: + def update_transparency( + self, widget: None, msgs: list[TopicMessage] + ) -> tuple[None, bool]: msgs_dict = {msg.name: msg.value for msg in msgs} - widget.alpha(msgs_dict["alpha_value"]) + widget.alpha(1.0 - msgs_dict["transparency_value"]) self.plotter.at(1).render() return widget, False -class AlphaSliderWidgetOps: - """WidgetOps implementation for the alpha transparency slider. +class TransparencySliderWidgetOps: + """WidgetOps implementation for the transparency slider. This widget provides a slider to control the transparency of the mesh - object. It publishes on the topic `alpha_value` a float value between 0.0 and 1.0. + object. It publishes on the topic `transparency_value` a float value between 0.0 + and 1.0. """ def __init__(self, plotter: Plotter) -> None: @@ -572,9 +575,9 @@ def __init__(self, plotter: Plotter) -> None: self._add_kwargs = { "xmin": 0.0, "xmax": 1.0, - "value": 1.0, + "value": 0.0, "pos": [(0.05, 0.0), (0.05, 0.4)], - "title": "Alpha", + "title": "Mesh Transparency", "title_size": 2, "slider_width": 0.04, "tube_width": 0.015, @@ -593,7 +596,7 @@ def set_state(self, widget: Slider2D, value: float) -> None: set_slider_state(widget, value) def state_to_messages(self, state: float) -> Iterable[TopicMessage]: - return [TopicMessage(name="alpha_value", value=state)] + return [TopicMessage(name="transparency_value", value=state)] class SensorPathButtonWidgetOps: @@ -1314,7 +1317,7 @@ class InteractivePlot: """An interactive plot for hypotheses and sampling bursts location. This visualizations features the following: - - A plot of the primary target mesh (with alpha transparency slider) with the agent + - A plot of the primary target mesh (with mesh transparency slider) with the agent and patch location on the object. - Buttons to activate or deactivate the history of agent and/or patch locations. - A plot of the pretrained model for the primary target with a button to show the @@ -1378,7 +1381,7 @@ def __init__( self._widgets["step_slider"].set_state(0) self._widgets["sensor_path_button"].set_state("No Sensor Path") self._widgets["patch_path_button"].set_state("No Patch Path") - self._widgets["alpha_slider"].set_state(1.0) + self._widgets["transparency_slider"].set_state(0.0) self._widgets["model_button"].set_state("Model") self._widgets["hyp_color_button"].set_state("None") self._widgets["hyp_scope_button"].set_state("No Hypotheses") @@ -1429,8 +1432,8 @@ def create_widgets(self): dedupe=True, ) - widgets["alpha_slider"] = Widget[Slider2D, float]( - widget_ops=AlphaSliderWidgetOps( + widgets["transparency_slider"] = Widget[Slider2D, float]( + widget_ops=TransparencySliderWidgetOps( plotter=self.plotter, ), bus=self.event_bus, From d5427f1c032ac33533c8c6e6a2371c03ab813187 Mon Sep 17 00:00:00 2001 From: ramyamounir <44840015+ramyamounir@users.noreply.github.com> Date: Tue, 2 Dec 2025 07:40:50 -0500 Subject: [PATCH 03/15] refactor!: rename sensor to agent --- ...interactive_hypothesis_space_pointcloud.py | 84 +++++++++---------- 1 file changed, 42 insertions(+), 42 deletions(-) diff --git a/src/tbp/plot/plots/interactive_hypothesis_space_pointcloud.py b/src/tbp/plot/plots/interactive_hypothesis_space_pointcloud.py index cfb46ae..53fb22e 100644 --- a/src/tbp/plot/plots/interactive_hypothesis_space_pointcloud.py +++ b/src/tbp/plot/plots/interactive_hypothesis_space_pointcloud.py @@ -281,15 +281,15 @@ def __init__( TopicSpec("episode_number", required=True), TopicSpec("step_number", required=True), ], - callback=self.update_sensor, + callback=self.update_agent, ), WidgetUpdater( topics=[ TopicSpec("episode_number", required=True), TopicSpec("step_number", required=True), - TopicSpec("sensor_path_button", required=True), + TopicSpec("agent_path_button", required=True), ], - callback=self.update_sensor_path, + callback=self.update_agent_path, ), WidgetUpdater( topics=[ @@ -308,11 +308,11 @@ def __init__( ] self._locators = self.create_locators() - self.sensor_sphere: Sphere | None = None + self.agent_sphere: Sphere | None = None self.gaze_line: Line | None = None - self.sensor_path_spheres: list[Sphere] = [] - self.sensor_path_line: Line | None = None + self.agent_path_spheres: list[Sphere] = [] + self.agent_path_line: Line | None = None self.patch_path_spheres: list[Sphere] = [] self.patch_path_line: Line | None = None @@ -334,7 +334,7 @@ def create_locators(self) -> dict[str, DataLocator]: ] ) - locators["sensor_location"] = DataLocator( + locators["agent_location"] = DataLocator( path=[ DataLocatorStep.key(name="episode"), DataLocatorStep.key(name="system", value="motor_system"), @@ -402,7 +402,7 @@ def update_mesh( return widget, False - def update_sensor( + def update_agent( self, widget: Mesh | None, msgs: list[TopicMessage] ) -> tuple[Mesh | None, bool]: """Update the agent and sensor patch location on the object. @@ -424,8 +424,8 @@ def update_sensor( ) mapping = np.flatnonzero(steps_mask) - sensor_pos = self.data_parser.extract( - self._locators["sensor_location"], + agent_pos = self.data_parser.extract( + self._locators["agent_location"], episode=str(episode_number), sm_step=int(mapping[step_number]), ) @@ -436,40 +436,40 @@ def update_sensor( step=step_number, ) - if self.sensor_sphere is None: - self.sensor_sphere = Sphere(pos=sensor_pos, r=0.004) - self.plotter.at(1).add(self.sensor_sphere) - self.sensor_sphere.pos(sensor_pos) + if self.agent_sphere is None: + self.agent_sphere = Sphere(pos=agent_pos, r=0.004) + self.plotter.at(1).add(self.agent_sphere) + self.agent_sphere.pos(agent_pos) if self.gaze_line is None: - self.gaze_line = Line(sensor_pos, patch_pos, c="black", lw=4) + self.gaze_line = Line(agent_pos, patch_pos, c="black", lw=4) self.plotter.at(1).add(self.gaze_line) - self.gaze_line.points = [sensor_pos, patch_pos] + self.gaze_line.points = [agent_pos, patch_pos] self.plotter.at(1).render() return widget, False - def _clear_sensor_path(self) -> None: - for s in self.sensor_path_spheres: + def _clear_agent_path(self) -> None: + for s in self.agent_path_spheres: self.plotter.at(1).remove(s) - self.sensor_path_spheres.clear() - if self.sensor_path_line is not None: - self.plotter.at(1).remove(self.sensor_path_line) - self.sensor_path_line = None + self.agent_path_spheres.clear() + if self.agent_path_line is not None: + self.plotter.at(1).remove(self.agent_path_line) + self.agent_path_line = None - def update_sensor_path( + def update_agent_path( self, widget: Mesh | None, msgs: list[TopicMessage] ) -> tuple[Mesh | None, bool]: msgs_dict = {msg.name: msg.value for msg in msgs} episode_number = msgs_dict["episode_number"] step_number = msgs_dict["step_number"] - path_button = msgs_dict["sensor_path_button"] + path_button = msgs_dict["agent_path_button"] # Clear existing path - self._clear_sensor_path() + self._clear_agent_path() - if path_button == "Sensor Path": + if path_button == "Agent Path": steps_mask = self.data_parser.extract( self._locators["steps_mask"], episode=str(episode_number) ) @@ -482,26 +482,26 @@ def update_sensor_path( # Clamp step_number to valid range max_step_idx = min(step_number, len(mapping) - 1) - # Collect all sensor positions up to the current step + # Collect all agent positions up to the current step points: list[np.ndarray] = [] for k in range(max_step_idx + 1): - sensor_pos = self.data_parser.extract( - self._locators["sensor_location"], + agent_pos = self.data_parser.extract( + self._locators["agent_location"], episode=str(episode_number), sm_step=int(mapping[k]), ) - points.append(sensor_pos) + points.append(agent_pos) # Create small spheres at each position for p in points: sphere = Sphere(pos=p, r=0.002) self.plotter.at(1).add(sphere) - self.sensor_path_spheres.append(sphere) + self.agent_path_spheres.append(sphere) # Create a polyline connecting all points if len(points) >= 2: - self.sensor_path_line = Line(points, c="red", lw=1) - self.plotter.at(1).add(self.sensor_path_line) + self.agent_path_line = Line(points, c="red", lw=1) + self.plotter.at(1).add(self.agent_path_line) self.plotter.at(1).render() return widget, False @@ -599,12 +599,12 @@ def state_to_messages(self, state: float) -> Iterable[TopicMessage]: return [TopicMessage(name="transparency_value", value=state)] -class SensorPathButtonWidgetOps: - """WidgetOps implementation for showing/hiding the sensor path. +class AgentPathButtonWidgetOps: + """WidgetOps implementation for showing/hiding the Agent path. This widget provides a button to switch between showing and hiding the - sensor path. The published state here is `Sensor Path` or `No Sensor Path` - and it is published on the topic `sensor_path_button`. + agent path. The published state here is `Agent Path` or `No Agent Path` + and it is published on the topic `agent_path_button`. """ def __init__(self, plotter: Plotter): @@ -612,7 +612,7 @@ def __init__(self, plotter: Plotter): self._add_kwargs = { "pos": (0.15, 0.98), - "states": ["Sensor Path", "No Sensor Path"], + "states": ["Agent Path", "No Agent Path"], "c": ["w", "w"], "bc": ["dg", "dr"], "size": 30, @@ -633,7 +633,7 @@ def set_state(self, widget: Button, value: str) -> None: def state_to_messages(self, state: str) -> Iterable[TopicMessage]: messages = [ - TopicMessage(name="sensor_path_button", value=state), + TopicMessage(name="agent_path_button", value=state), ] return messages @@ -1379,7 +1379,7 @@ def __init__( for w in self._widgets.values(): w.add() self._widgets["step_slider"].set_state(0) - self._widgets["sensor_path_button"].set_state("No Sensor Path") + self._widgets["agent_path_button"].set_state("No Agent Path") self._widgets["patch_path_button"].set_state("No Patch Path") self._widgets["transparency_slider"].set_state(0.0) self._widgets["model_button"].set_state("Model") @@ -1442,8 +1442,8 @@ def create_widgets(self): dedupe=True, ) - widgets["sensor_path_button"] = Widget[Button, str]( - widget_ops=SensorPathButtonWidgetOps(plotter=self.plotter), + widgets["agent_path_button"] = Widget[Button, str]( + widget_ops=AgentPathButtonWidgetOps(plotter=self.plotter), bus=self.event_bus, scheduler=self.scheduler, debounce_sec=0.2, From c85b16c761fd34eecfed7f93b5edef5d849389b9 Mon Sep 17 00:00:00 2001 From: ramyamounir <44840015+ramyamounir@users.noreply.github.com> Date: Tue, 2 Dec 2025 11:57:21 -0500 Subject: [PATCH 04/15] refactor!: rename buttons to "Name: [on|off]" --- ...interactive_hypothesis_space_pointcloud.py | 34 +++++++++---------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/src/tbp/plot/plots/interactive_hypothesis_space_pointcloud.py b/src/tbp/plot/plots/interactive_hypothesis_space_pointcloud.py index 53fb22e..e7ddb8c 100644 --- a/src/tbp/plot/plots/interactive_hypothesis_space_pointcloud.py +++ b/src/tbp/plot/plots/interactive_hypothesis_space_pointcloud.py @@ -469,7 +469,7 @@ def update_agent_path( # Clear existing path self._clear_agent_path() - if path_button == "Agent Path": + if path_button == "Agent Path: On": steps_mask = self.data_parser.extract( self._locators["steps_mask"], episode=str(episode_number) ) @@ -525,7 +525,7 @@ def update_patch_path( # Clear existing path self._clear_patch_path() - if path_button == "Patch Path": + if path_button == "Patch Path: On": # Collect all patch positions up to the current step points: list[np.ndarray] = [] max_step_idx = max(step_number, 0) @@ -603,7 +603,7 @@ class AgentPathButtonWidgetOps: """WidgetOps implementation for showing/hiding the Agent path. This widget provides a button to switch between showing and hiding the - agent path. The published state here is `Agent Path` or `No Agent Path` + agent path. The published state here is `Agent Path: On` or `Agent Path: Off` and it is published on the topic `agent_path_button`. """ @@ -612,7 +612,7 @@ def __init__(self, plotter: Plotter): self._add_kwargs = { "pos": (0.15, 0.98), - "states": ["Agent Path", "No Agent Path"], + "states": ["Agent Path: On", "Agent Path: Off"], "c": ["w", "w"], "bc": ["dg", "dr"], "size": 30, @@ -642,7 +642,7 @@ class PatchPathButtonWidgetOps: """WidgetOps implementation for showing/hiding the sensor patch path. This widget provides a button to switch between showing and hiding the - patch path. The published state here is `Patch Path` or `No Patch Path` + patch path. The published state here is `Patch Path: On` or `Patch Path: Off` and it is published on the topic `patch_path_button`. """ @@ -651,7 +651,7 @@ def __init__(self, plotter: Plotter): self._add_kwargs = { "pos": (0.35, 0.98), - "states": ["Patch Path", "No Patch Path"], + "states": ["Patch Path: On", "Patch Path: Off"], "c": ["w", "w"], "bc": ["dg", "dr"], "size": 30, @@ -756,7 +756,7 @@ def update_model( step_number = msgs_dict["step_number"] model_button = msgs_dict["model_button"] - if model_button == "Model": + if model_button == "Pretrained Model: On": locator = self._locators["target"] target = self.data_parser.extract(locator, episode=str(episode_number)) target_id = target["primary_target_object"] @@ -851,7 +851,7 @@ def update_hypotheses( self._clear_hyp_space() - if hyp_scope_button != "No Hypotheses": + if hyp_scope_button != "Hypotheses: Off": hyp_space = self._create_hyp_space( episode_number, step_number, hyp_color_button, hyp_scope_button ) @@ -869,8 +869,8 @@ def __init__(self, plotter: Plotter): self.plotter = plotter self._add_kwargs = { - "pos": (0.6, 0.98), - "states": ["Model", "No Model"], + "pos": (0.62, 0.98), + "states": ["Pretrained Model: On", "Pretrained Model: Off"], "c": ["w", "w"], "bc": ["dg", "dr"], "size": 30, @@ -904,7 +904,7 @@ def __init__(self, plotter: Plotter): self._add_kwargs = { "pos": (0.85, 0.98), - "states": ["No Hypotheses", "Hypotheses"], + "states": ["Hypotheses: Off", "Hypotheses: On"], "c": ["w", "w"], "bc": ["dr", "dg"], "size": 30, @@ -982,10 +982,10 @@ def toggle_button( ) -> tuple[Button, bool]: msgs_dict = {msg.name: msg.value for msg in msgs} - if msgs_dict["hyp_scope_button"] == "No Hypotheses": + if msgs_dict["hyp_scope_button"] == "Hypotheses: Off": # Hide the button by moving it outside of the scene widget.SetPosition(-10.0, -10.0) - elif msgs_dict["hyp_scope_button"] == "Hypotheses": + elif msgs_dict["hyp_scope_button"] == "Hypotheses: On": x, y = self._add_kwargs["pos"] widget.SetPosition(x, y) @@ -1379,12 +1379,12 @@ def __init__( for w in self._widgets.values(): w.add() self._widgets["step_slider"].set_state(0) - self._widgets["agent_path_button"].set_state("No Agent Path") - self._widgets["patch_path_button"].set_state("No Patch Path") + self._widgets["agent_path_button"].set_state("Agent Path: Off") + self._widgets["patch_path_button"].set_state("Patch Path: Off") self._widgets["transparency_slider"].set_state(0.0) - self._widgets["model_button"].set_state("Model") + self._widgets["model_button"].set_state("Pretrained Model: On") self._widgets["hyp_color_button"].set_state("None") - self._widgets["hyp_scope_button"].set_state("No Hypotheses") + self._widgets["hyp_scope_button"].set_state("Hypotheses: Off") self.plotter.at(0).show( camera=deepcopy(self.cam_dict), From 97faf911575e9f3ebc85934da7600c389a9f472f Mon Sep 17 00:00:00 2001 From: ramyamounir <44840015+ramyamounir@users.noreply.github.com> Date: Tue, 2 Dec 2025 12:10:19 -0500 Subject: [PATCH 05/15] chore: reposition some buttons --- .../plot/plots/interactive_hypothesis_space_pointcloud.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/tbp/plot/plots/interactive_hypothesis_space_pointcloud.py b/src/tbp/plot/plots/interactive_hypothesis_space_pointcloud.py index e7ddb8c..1cc26a3 100644 --- a/src/tbp/plot/plots/interactive_hypothesis_space_pointcloud.py +++ b/src/tbp/plot/plots/interactive_hypothesis_space_pointcloud.py @@ -611,7 +611,7 @@ def __init__(self, plotter: Plotter): self.plotter = plotter self._add_kwargs = { - "pos": (0.15, 0.98), + "pos": (0.16, 0.98), "states": ["Agent Path: On", "Agent Path: Off"], "c": ["w", "w"], "bc": ["dg", "dr"], @@ -650,7 +650,7 @@ def __init__(self, plotter: Plotter): self.plotter = plotter self._add_kwargs = { - "pos": (0.35, 0.98), + "pos": (0.37, 0.98), "states": ["Patch Path: On", "Patch Path: Off"], "c": ["w", "w"], "bc": ["dg", "dr"], @@ -869,7 +869,7 @@ def __init__(self, plotter: Plotter): self.plotter = plotter self._add_kwargs = { - "pos": (0.62, 0.98), + "pos": (0.63, 0.98), "states": ["Pretrained Model: On", "Pretrained Model: Off"], "c": ["w", "w"], "bc": ["dg", "dr"], From 784d6fb0bffd40fa97cf10e903ecd34d30547bcb Mon Sep 17 00:00:00 2001 From: ramyamounir <44840015+ramyamounir@users.noreply.github.com> Date: Tue, 2 Dec 2025 13:32:07 -0500 Subject: [PATCH 06/15] feat: add tbp color palette --- src/tbp/interactive/colors.py | 75 ++++++++++++++++++++++++++ tests/unit/interactive/colors_test.py | 77 +++++++++++++++++++++++++++ 2 files changed, 152 insertions(+) create mode 100644 src/tbp/interactive/colors.py create mode 100644 tests/unit/interactive/colors_test.py diff --git a/src/tbp/interactive/colors.py b/src/tbp/interactive/colors.py new file mode 100644 index 0000000..aea90a3 --- /dev/null +++ b/src/tbp/interactive/colors.py @@ -0,0 +1,75 @@ +# Copyright 2025 Thousand Brains Project +# +# Copyright may exist in Contributors' modifications +# and/or contributions to the work. +# +# Use of this source code is governed by the MIT +# license that can be found in the LICENSE file or at +# https://opensource.org/licenses/MIT. +from __future__ import annotations + +from dataclasses import dataclass + +Color = str | tuple[float, float, float] | tuple[float, float, float, float] + + +def hex_to_rgb(hex_str: str, alpha: float | None = None) -> Color: + hex_clean = hex_str.lstrip("#") + if len(hex_clean) != 6: + raise ValueError(f"Expected 6 hex digits, got: {hex_str!r}") + + r = int(hex_clean[0:2], 16) / 255.0 + g = int(hex_clean[2:4], 16) / 255.0 + b = int(hex_clean[4:6], 16) / 255.0 + + if alpha is None: + return (r, g, b) + return (r, g, b, alpha) + + +@dataclass(frozen=True) +class Palette: + """The TBP color palette. + + If you request a color that doesn't exist, a KeyError is raised with a list of + available names. + """ + + # Primary Colors + indigo: str = "#2f2b5c" + numenta_blue: str = "#00a0df" + + # Secondary Colors + bossanova: str = "#5c315f" + vivid_violet: str = "#86308b" + blue_violet: str = "#655eb2" + amethyst: str = "#915acc" + + # Accent Colors/Shades + rich_black: str = "#000000" + charcoal: str = "#3f3f3f" + link_water: str = "#dfe6f5" + + # ---------- Internal helper ---------- + @classmethod + def _validate(cls, name: str) -> str: + if not hasattr(cls, name): + available = [k for k in cls.__dict__.keys() if not k.startswith("_")] + msg = ( + f"Color '{name}' is not defined in Palette.\n" + f"Available colors: {', '.join(available)}" + ) + raise KeyError(msg) + return getattr(cls, name) + + # ---------- Public API ---------- + @classmethod + def as_hex(cls, name: str) -> Color: + """Return the raw hex string for a color name.""" + return cls._validate(name) + + @classmethod + def as_rgb(cls, name: str, alpha: float | None = None) -> Color: + """Return the color as an RGB(A) tuple in [0,1] range.""" + hex_str = cls._validate(name) + return hex_to_rgb(hex_str, alpha=alpha) diff --git a/tests/unit/interactive/colors_test.py b/tests/unit/interactive/colors_test.py new file mode 100644 index 0000000..7bbd275 --- /dev/null +++ b/tests/unit/interactive/colors_test.py @@ -0,0 +1,77 @@ +# Copyright 2025 Thousand Brains Project +# +# Copyright may exist in Contributors' modifications +# and/or contributions to the work. +# +# Use of this source code is governed by the MIT +# license that can be found in the LICENSE file or at +# https://opensource.org/licenses/MIT. +import unittest + +from tbp.interactive.colors import Palette, hex_to_rgb + + +class HexToRgbTests(unittest.TestCase): + def test_rgb_without_alpha(self) -> None: + self.assertEqual(hex_to_rgb("#ffffff"), (1.0, 1.0, 1.0)) + self.assertEqual(hex_to_rgb("#000000"), (0.0, 0.0, 0.0)) + self.assertEqual(hex_to_rgb("#ff0000"), (1.0, 0.0, 0.0)) + + def test_rgb_without_hash_prefix(self) -> None: + self.assertEqual(hex_to_rgb("00ff00"), (0.0, 1.0, 0.0)) + + def test_rgb_with_alpha(self) -> None: + result = hex_to_rgb("#336699", alpha=0.5) + self.assertEqual(result, (0.2, 0.4, 0.6, 0.5)) + + def test_invalid_hex_length_raises(self) -> None: + with self.assertRaises(ValueError): + hex_to_rgb("#12345") + + +class PaletteTests(unittest.TestCase): + def setUp(self) -> None: + self.valid_color = "numenta_blue" + + def test_as_hex_returns_string(self) -> None: + """Tests if the returned hex is valid hex code.""" + hex_value = Palette.as_hex(self.valid_color) + self.assertIsInstance(hex_value, str) + self.assertRegex(hex_value, r"#?[0-9A-Fa-f]{6}") + + def test_as_rgb_returns_rgb_tuple(self) -> None: + rgb = Palette.as_rgb(self.valid_color) + self.assertIsInstance(rgb, tuple) + self.assertEqual(len(rgb), 3) + for channel in rgb: + self.assertGreaterEqual(channel, 0.0) + self.assertLessEqual(channel, 1.0) + + def test_as_rgb_with_alpha_returns_rgba_tuple(self) -> None: + rgba = Palette.as_rgb(self.valid_color, alpha=0.8) + self.assertIsInstance(rgba, tuple) + self.assertEqual(len(rgba), 4) + r, g, b, a = rgba + for channel in (r, g, b): + self.assertGreaterEqual(channel, 0.0) + self.assertLessEqual(channel, 1.0) + self.assertAlmostEqual(a, 0.8) + + def test_as_hex_unknown_color_raises_keyerror(self) -> None: + with self.assertRaises(KeyError) as ctx: + Palette.as_hex("banana_yellow") + + def test_as_rgb_unknown_color_raises_keyerror(self) -> None: + with self.assertRaises(KeyError) as ctx: + Palette.as_rgb("not_a_real_color") + + msg = str(ctx.exception) + self.assertIn("not_a_real_color", msg) + self.assertIn("Available colors:", msg) + + def tearDown(self) -> None: + self.valid_color = None + + +if __name__ == "__main__": + unittest.main() From b9aa9135fa09fcb8589ceb1a4d8d45cac3c96e0f Mon Sep 17 00:00:00 2001 From: ramyamounir <44840015+ramyamounir@users.noreply.github.com> Date: Tue, 2 Dec 2025 13:32:24 -0500 Subject: [PATCH 07/15] refactor: change colors to use tbp palette --- ...interactive_hypothesis_space_pointcloud.py | 63 ++++++++++++++----- 1 file changed, 46 insertions(+), 17 deletions(-) diff --git a/src/tbp/plot/plots/interactive_hypothesis_space_pointcloud.py b/src/tbp/plot/plots/interactive_hypothesis_space_pointcloud.py index 1cc26a3..0e3bc27 100644 --- a/src/tbp/plot/plots/interactive_hypothesis_space_pointcloud.py +++ b/src/tbp/plot/plots/interactive_hypothesis_space_pointcloud.py @@ -21,6 +21,7 @@ from pubsub.core import Publisher from vedo import Button, Image, Line, Mesh, Plotter, Points, Slider2D, Sphere +from tbp.interactive.colors import Palette from tbp.interactive.data import ( DataLocator, DataLocatorStep, @@ -437,12 +438,16 @@ def update_agent( ) if self.agent_sphere is None: - self.agent_sphere = Sphere(pos=agent_pos, r=0.004) + self.agent_sphere = Sphere( + pos=agent_pos, r=0.004, c=Palette.as_hex("vivid_violet") + ) self.plotter.at(1).add(self.agent_sphere) self.agent_sphere.pos(agent_pos) if self.gaze_line is None: - self.gaze_line = Line(agent_pos, patch_pos, c="black", lw=4) + self.gaze_line = Line( + agent_pos, patch_pos, c=Palette.as_hex("rich_black"), lw=4 + ) self.plotter.at(1).add(self.gaze_line) self.gaze_line.points = [agent_pos, patch_pos] @@ -494,13 +499,15 @@ def update_agent_path( # Create small spheres at each position for p in points: - sphere = Sphere(pos=p, r=0.002) + sphere = Sphere(pos=p, r=0.002, c=Palette.as_hex("vivid_violet")) self.plotter.at(1).add(sphere) self.agent_path_spheres.append(sphere) # Create a polyline connecting all points if len(points) >= 2: - self.agent_path_line = Line(points, c="red", lw=1) + self.agent_path_line = Line( + points, c=Palette.as_hex("vivid_violet"), lw=1 + ) self.plotter.at(1).add(self.agent_path_line) self.plotter.at(1).render() @@ -540,13 +547,15 @@ def update_patch_path( # Create small black spheres at each patch position for p in points: - sphere = Sphere(pos=p, r=0.002, c="black") + sphere = Sphere(pos=p, r=0.002, c=Palette.as_hex("rich_black")) self.plotter.at(1).add(sphere) self.patch_path_spheres.append(sphere) # Create a thin black polyline connecting all patch positions if len(points) >= 2: - self.patch_path_line = Line(points, c="black", lw=1) + self.patch_path_line = Line( + points, c=Palette.as_hex("rich_black"), lw=1 + ) self.plotter.at(1).add(self.patch_path_line) self.plotter.at(1).render() @@ -614,7 +623,7 @@ def __init__(self, plotter: Plotter): "pos": (0.16, 0.98), "states": ["Agent Path: On", "Agent Path: Off"], "c": ["w", "w"], - "bc": ["dg", "dr"], + "bc": [Palette.as_hex("numenta_blue"), Palette.as_hex("vivid_violet")], "size": 30, "font": "Calco", "bold": True, @@ -653,7 +662,7 @@ def __init__(self, plotter: Plotter): "pos": (0.37, 0.98), "states": ["Patch Path: On", "Patch Path: Off"], "c": ["w", "w"], - "bc": ["dg", "dr"], + "bc": [Palette.as_hex("numenta_blue"), Palette.as_hex("vivid_violet")], "size": 30, "font": "Calco", "bold": True, @@ -815,7 +824,7 @@ def _create_hyp_space( evidences, locations, pose_errors, ages, slopes = self._extract_obj_telemetry( episode_number, step_number, curr_object ) - pts = Points(np.array(locations), r=6, c="dg") + pts = Points(np.array(locations), r=6, c=Palette.as_hex("vivid_violet")) if hyp_color_button == "Evidence": pts.cmap("viridis", evidences, vmin=0.0) @@ -824,7 +833,9 @@ def _create_hyp_space( mlh = np.zeros_like(evidences, dtype=float) pts.cmap("viridis", mlh, vmin=0.0, vmax=1.0) mlh_sphere = Sphere( - pos=locations[int(np.argmax(evidences))], r=0.002, c="yellow" + pos=locations[int(np.argmax(evidences))], + r=0.002, + c=Palette.as_hex("numenta_blue"), ) self.mlh_sphere = mlh_sphere self.plotter.at(2).add(mlh_sphere) @@ -872,7 +883,7 @@ def __init__(self, plotter: Plotter): "pos": (0.63, 0.98), "states": ["Pretrained Model: On", "Pretrained Model: Off"], "c": ["w", "w"], - "bc": ["dg", "dr"], + "bc": [Palette.as_hex("numenta_blue"), Palette.as_hex("vivid_violet")], "size": 30, "font": "Calco", "bold": True, @@ -904,9 +915,9 @@ def __init__(self, plotter: Plotter): self._add_kwargs = { "pos": (0.85, 0.98), - "states": ["Hypotheses: Off", "Hypotheses: On"], + "states": ["Hypotheses: On", "Hypotheses: Off"], "c": ["w", "w"], - "bc": ["dr", "dg"], + "bc": [Palette.as_hex("numenta_blue"), Palette.as_hex("vivid_violet")], "size": 30, "font": "Calco", "bold": True, @@ -945,7 +956,14 @@ def __init__(self, plotter: Plotter): "pos": (0.18, 0.15), "states": ["None", "Evidence", "MLH", "Pose Error", "Slope", "Ages"], "c": ["w", "w", "w", "w", "w", "w"], - "bc": ["gray", "dg", "ds", "dm", "db", "dc"], + "bc": [ + Palette.as_hex("link_water"), + Palette.as_hex("numenta_blue"), + Palette.as_hex("vivid_violet"), + Palette.as_hex("bossanova"), + Palette.as_hex("charcoal"), + Palette.as_hex("amethyst"), + ], "size": 30, "font": "Calco", "bold": True, @@ -1162,9 +1180,20 @@ def _create_burst_figure(self, global_step: int) -> plt.Figure: start_idx = int(valid_idx[0]) if valid_idx.size > 0 else 0 if start_idx < len(slopes): - ax_left.plot(x[start_idx:], slopes[start_idx:], label="Max slope") + ax_left.plot( + x[start_idx:], + slopes[start_idx:], + color=Palette.as_hex("numenta_blue"), + label="Max slope", + ) - ax_right.plot(x, hyp_space_sizes, linestyle="--", label="Hypothesis space size") + ax_right.plot( + x, + hyp_space_sizes, + linestyle="--", + color=Palette.as_hex("numenta_blue"), + label="Hypothesis space size", + ) ax_left.set_ylim(-1.0, 2.0) ax_right.set_ylim(0, 10000) @@ -1177,7 +1206,7 @@ def _create_burst_figure(self, global_step: int) -> plt.Figure: add_idx, ymin, ymax, - colors="red", + colors=Palette.as_hex("vivid_violet"), linestyles="--", alpha=1.0, linewidth=1.0, From 7a96a50c215b995528c4e3c3f38fb3c1743128cc Mon Sep 17 00:00:00 2001 From: ramyamounir <44840015+ramyamounir@users.noreply.github.com> Date: Tue, 2 Dec 2025 13:38:15 -0500 Subject: [PATCH 08/15] fix: minor typing fix --- .../plot/plots/interactive_hypothesis_space_pointcloud.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/tbp/plot/plots/interactive_hypothesis_space_pointcloud.py b/src/tbp/plot/plots/interactive_hypothesis_space_pointcloud.py index 0e3bc27..78d7324 100644 --- a/src/tbp/plot/plots/interactive_hypothesis_space_pointcloud.py +++ b/src/tbp/plot/plots/interactive_hypothesis_space_pointcloud.py @@ -565,8 +565,9 @@ def update_transparency( self, widget: None, msgs: list[TopicMessage] ) -> tuple[None, bool]: msgs_dict = {msg.name: msg.value for msg in msgs} - widget.alpha(1.0 - msgs_dict["transparency_value"]) - self.plotter.at(1).render() + if widget is not None: + widget.alpha(1.0 - msgs_dict["transparency_value"]) + self.plotter.at(1).render() return widget, False From 8495f5d04f1094062d9096e006746a7730733bb0 Mon Sep 17 00:00:00 2001 From: ramyamounir <44840015+ramyamounir@users.noreply.github.com> Date: Tue, 2 Dec 2025 13:54:55 -0500 Subject: [PATCH 09/15] feat: increase figure dpi to 200 --- src/tbp/plot/plots/interactive_hypothesis_space_pointcloud.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/tbp/plot/plots/interactive_hypothesis_space_pointcloud.py b/src/tbp/plot/plots/interactive_hypothesis_space_pointcloud.py index 78d7324..6e935bf 100644 --- a/src/tbp/plot/plots/interactive_hypothesis_space_pointcloud.py +++ b/src/tbp/plot/plots/interactive_hypothesis_space_pointcloud.py @@ -1174,7 +1174,7 @@ def _create_burst_figure(self, global_step: int) -> plt.Figure: x = np.arange(len(slopes)) - fig, ax_left = plt.subplots(1, 1, figsize=(14, 3)) + fig, ax_left = plt.subplots(1, 1, figsize=(14, 3), dpi=200) ax_right = ax_left.twinx() valid_idx = np.where(~np.isnan(slopes))[0] @@ -1266,6 +1266,7 @@ def update_plot( global_step = self.step_mapper.local_to_global(episode_number, step_number) widget = self._create_burst_figure(global_step) + widget.scale(0.5) widget.pos(-400, -150, 0) self.plotter.at(0).add(widget) From ec627f735e47cf026da083405cdcd21cb6ebcecf Mon Sep 17 00:00:00 2001 From: ramyamounir <44840015+ramyamounir@users.noreply.github.com> Date: Wed, 3 Dec 2025 06:56:20 -0500 Subject: [PATCH 10/15] refactor: extract FONT constant --- .../plots/interactive_hypothesis_space_pointcloud.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/tbp/plot/plots/interactive_hypothesis_space_pointcloud.py b/src/tbp/plot/plots/interactive_hypothesis_space_pointcloud.py index 6e935bf..080c3db 100644 --- a/src/tbp/plot/plots/interactive_hypothesis_space_pointcloud.py +++ b/src/tbp/plot/plots/interactive_hypothesis_space_pointcloud.py @@ -50,6 +50,8 @@ logger = logging.getLogger(__name__) +FONT = "Calco" + class StepMapper: """Bidirectional mapping between global step indices and (episode, local_step). @@ -626,7 +628,7 @@ def __init__(self, plotter: Plotter): "c": ["w", "w"], "bc": [Palette.as_hex("numenta_blue"), Palette.as_hex("vivid_violet")], "size": 30, - "font": "Calco", + "font": FONT, "bold": True, } @@ -665,7 +667,7 @@ def __init__(self, plotter: Plotter): "c": ["w", "w"], "bc": [Palette.as_hex("numenta_blue"), Palette.as_hex("vivid_violet")], "size": 30, - "font": "Calco", + "font": FONT, "bold": True, } @@ -886,7 +888,7 @@ def __init__(self, plotter: Plotter): "c": ["w", "w"], "bc": [Palette.as_hex("numenta_blue"), Palette.as_hex("vivid_violet")], "size": 30, - "font": "Calco", + "font": FONT, "bold": True, } @@ -920,7 +922,7 @@ def __init__(self, plotter: Plotter): "c": ["w", "w"], "bc": [Palette.as_hex("numenta_blue"), Palette.as_hex("vivid_violet")], "size": 30, - "font": "Calco", + "font": FONT, "bold": True, } @@ -966,7 +968,7 @@ def __init__(self, plotter: Plotter): Palette.as_hex("amethyst"), ], "size": 30, - "font": "Calco", + "font": FONT, "bold": True, } From 39ba30d31c20b251a63bcbd2f4fad849b729a22a Mon Sep 17 00:00:00 2001 From: ramyamounir <44840015+ramyamounir@users.noreply.github.com> Date: Wed, 3 Dec 2025 08:38:43 -0500 Subject: [PATCH 11/15] refactor: change scalarbar font and font sizes extraction --- ...interactive_hypothesis_space_pointcloud.py | 46 ++++++++++++------- 1 file changed, 30 insertions(+), 16 deletions(-) diff --git a/src/tbp/plot/plots/interactive_hypothesis_space_pointcloud.py b/src/tbp/plot/plots/interactive_hypothesis_space_pointcloud.py index 080c3db..6e4436d 100644 --- a/src/tbp/plot/plots/interactive_hypothesis_space_pointcloud.py +++ b/src/tbp/plot/plots/interactive_hypothesis_space_pointcloud.py @@ -50,7 +50,8 @@ logger = logging.getLogger(__name__) -FONT = "Calco" +FONT = "Arial" +FONT_SIZE = 25 class StepMapper: @@ -216,6 +217,7 @@ def __init__( "value": 0, "pos": [(0.11, 0.06), (0.89, 0.06)], "title": "Step", + "font": FONT, "show_value": False, } @@ -592,6 +594,7 @@ def __init__(self, plotter: Plotter) -> None: "title": "Mesh Transparency", "title_size": 2, "slider_width": 0.04, + "font": FONT, "tube_width": 0.015, } @@ -627,9 +630,9 @@ def __init__(self, plotter: Plotter): "states": ["Agent Path: On", "Agent Path: Off"], "c": ["w", "w"], "bc": [Palette.as_hex("numenta_blue"), Palette.as_hex("vivid_violet")], - "size": 30, + "size": FONT_SIZE, "font": FONT, - "bold": True, + "bold": False, } def add(self, callback: Callable) -> Button: @@ -666,9 +669,9 @@ def __init__(self, plotter: Plotter): "states": ["Patch Path: On", "Patch Path: Off"], "c": ["w", "w"], "bc": [Palette.as_hex("numenta_blue"), Palette.as_hex("vivid_violet")], - "size": 30, + "size": FONT_SIZE, "font": FONT, - "bold": True, + "bold": False, } def add(self, callback: Callable) -> Button: @@ -814,6 +817,13 @@ def _extract_obj_telemetry( return evidences, locations, pose_errors, ages, slopes + def _change_scalarbar_font(self, sb): + sb_text = sb.GetLabelTextProperty() + sb_text.SetFontFamilyAsString(FONT) + sb_text.SetColor((0.2, 0.2, 0.2)) + sb_text.SetBold(0) + sb_text.SetItalic(0) + def _create_hyp_space( self, episode_number: int, @@ -831,7 +841,8 @@ def _create_hyp_space( if hyp_color_button == "Evidence": pts.cmap("viridis", evidences, vmin=0.0) - pts.add_scalarbar(title="Evidence") + pts.add_scalarbar(title="", pos=[[0.8, 0.3], [0.9, 0.9]]) + self._change_scalarbar_font(pts.scalarbar) if hyp_color_button == "MLH": mlh = np.zeros_like(evidences, dtype=float) pts.cmap("viridis", mlh, vmin=0.0, vmax=1.0) @@ -844,13 +855,16 @@ def _create_hyp_space( self.plotter.at(2).add(mlh_sphere) elif hyp_color_button == "Slope": pts.cmap("viridis", slopes, vmin=0.0) - pts.add_scalarbar(title="Slope") + pts.add_scalarbar(title="", pos=[[0.8, 0.3], [0.9, 0.9]]) + self._change_scalarbar_font(pts.scalarbar) elif hyp_color_button == "Ages": pts.cmap("viridis", ages, vmin=0.0) - pts.add_scalarbar(title="Age") + pts.add_scalarbar(title="", pos=[[0.8, 0.3], [0.9, 0.9]]) + self._change_scalarbar_font(pts.scalarbar) elif hyp_color_button == "Pose Error": pts.cmap("viridis", pose_errors, vmin=0.0) - pts.add_scalarbar(title="Pose Error") + pts.add_scalarbar(title="", pos=[[0.8, 0.3], [0.9, 0.9]]) + self._change_scalarbar_font(pts.scalarbar) return pts @@ -887,9 +901,9 @@ def __init__(self, plotter: Plotter): "states": ["Pretrained Model: On", "Pretrained Model: Off"], "c": ["w", "w"], "bc": [Palette.as_hex("numenta_blue"), Palette.as_hex("vivid_violet")], - "size": 30, + "size": FONT_SIZE, "font": FONT, - "bold": True, + "bold": False, } def add(self, callback: Callable) -> Button: @@ -921,9 +935,9 @@ def __init__(self, plotter: Plotter): "states": ["Hypotheses: On", "Hypotheses: Off"], "c": ["w", "w"], "bc": [Palette.as_hex("numenta_blue"), Palette.as_hex("vivid_violet")], - "size": 30, + "size": FONT_SIZE, "font": FONT, - "bold": True, + "bold": False, } def add(self, callback: Callable) -> Button: @@ -956,7 +970,7 @@ def __init__(self, plotter: Plotter): self.plotter = plotter self._add_kwargs = { - "pos": (0.18, 0.15), + "pos": (0.88, 0.2), "states": ["None", "Evidence", "MLH", "Pose Error", "Slope", "Ages"], "c": ["w", "w", "w", "w", "w", "w"], "bc": [ @@ -967,9 +981,9 @@ def __init__(self, plotter: Plotter): Palette.as_hex("charcoal"), Palette.as_hex("amethyst"), ], - "size": 30, + "size": FONT_SIZE - 5, "font": FONT, - "bold": True, + "bold": False, } self.updaters = [ From f71204a9b240ab8aca585cbd7f0d026c8355a5a5 Mon Sep 17 00:00:00 2001 From: ramyamounir <44840015+ramyamounir@users.noreply.github.com> Date: Mon, 8 Dec 2025 11:00:37 -0500 Subject: [PATCH 12/15] feat: add rectangles and plotting mods --- ...interactive_hypothesis_space_pointcloud.py | 107 ++++++++++++++++-- 1 file changed, 99 insertions(+), 8 deletions(-) diff --git a/src/tbp/plot/plots/interactive_hypothesis_space_pointcloud.py b/src/tbp/plot/plots/interactive_hypothesis_space_pointcloud.py index 6e4436d..d0b25ed 100644 --- a/src/tbp/plot/plots/interactive_hypothesis_space_pointcloud.py +++ b/src/tbp/plot/plots/interactive_hypothesis_space_pointcloud.py @@ -18,6 +18,9 @@ import matplotlib.pyplot as plt import numpy as np import pandas as pd +from matplotlib import transforms +from matplotlib.lines import Line2D +from matplotlib.patches import Rectangle from pubsub.core import Publisher from vedo import Button, Image, Line, Mesh, Plotter, Points, Slider2D, Sphere @@ -1109,6 +1112,19 @@ def remove(self, widget: Mesh) -> None: if widget is not None: self.plotter.at(0).remove(widget) + def _get_object_segments(self) -> list[tuple[int, int, str]]: + segments = [] + global_offset = 0 + for episode in self.data_parser.query(self._locators["evidence"]): + obj = self.data_parser.extract(self._locators["target"], episode=episode) + num_steps = len( + self.data_parser.query(self._locators["evidence"], episode=episode) + ) + segments.append((global_offset, global_offset + num_steps - 1, obj)) + global_offset += num_steps + + return segments + def _get_bursts(self) -> list[bool]: bursts = [] for episode in self.data_parser.query(self._locators["evidence"]): @@ -1193,9 +1209,9 @@ def _create_burst_figure(self, global_step: int) -> plt.Figure: fig, ax_left = plt.subplots(1, 1, figsize=(14, 3), dpi=200) ax_right = ax_left.twinx() + # Max slopes don't start from 0 valid_idx = np.where(~np.isnan(slopes))[0] start_idx = int(valid_idx[0]) if valid_idx.size > 0 else 0 - if start_idx < len(slopes): ax_left.plot( x[start_idx:], @@ -1212,6 +1228,17 @@ def _create_burst_figure(self, global_step: int) -> plt.Figure: label="Hypothesis space size", ) + # Fill area under the curve for hyp space size + ax_right.fill_between( + x, + hyp_space_sizes, + 0.0, + alpha=0.15, + color=Palette.as_hex("numenta_blue"), + zorder=0, + ) + + # Set axes limits ax_left.set_ylim(-1.0, 2.0) ax_right.set_ylim(0, 10000) @@ -1248,24 +1275,88 @@ def _create_burst_figure(self, global_step: int) -> plt.Figure: ax_left.set_ylabel("Max slope") ax_right.set_ylabel("Hyp space size") - # Merge legends and place above + # Collect legend entries lines_left, labels_left = ax_left.get_legend_handles_labels() lines_right, labels_right = ax_right.get_legend_handles_labels() + all_lines = lines_left + lines_right all_labels = labels_left + labels_right - if all_lines: + # Create two legend boxes above the plot + data_lines: list[Line2D] = [] + data_labels: list[str] = [] + time_lines: list[Line2D] = [] + time_labels: list[str] = [] + + for line, label in zip(all_lines, all_labels, strict=True): + if label in ("Max slope", "Hypothesis space size"): + data_lines.append(line) + data_labels.append(label) + elif label in ("Burst", "Current step"): + time_lines.append(line) + time_labels.append(label) + + if data_lines: + legend_metrics = ax_left.legend( + data_lines, + data_labels, + loc="upper left", + bbox_to_anchor=(0.0, 1.30), + frameon=False, + ncol=len(data_lines), + columnspacing=1.0, + handletextpad=0.5, + ) + ax_left.add_artist(legend_metrics) + + if time_lines: ax_left.legend( - all_lines, - all_labels, - loc="upper center", - bbox_to_anchor=(0.5, 1.20), - ncol=len(all_lines), + time_lines, + time_labels, + loc="upper right", + bbox_to_anchor=(1.0, 1.30), frameon=False, + ncol=len(time_lines), columnspacing=1.0, handletextpad=0.5, ) + # Plot the object rectangles above the figure + object_segments = self._get_object_segments() + transform = transforms.blended_transform_factory( + ax_left.transData, ax_left.transAxes + ) + + rect_y = 1.02 # just above the top of the axes (y = 1) + rect_h = 0.08 # height in axes fraction + + for start, end, name in object_segments: + rect = Rectangle( + (start, rect_y), + width=(end - start), + height=rect_h, + transform=transform, + facecolor="white", + edgecolor="black", + linewidth=1.2, + zorder=10, + clip_on=False, + ) + ax_left.add_patch(rect) + + ax_left.text( + (start + end) / 2, + rect_y + rect_h / 2, + name, + transform=transform, + ha="center", + va="center", + fontsize=10, + color="black", + zorder=11, + clip_on=False, + ) + fig.tight_layout(rect=[0, 0, 1, 0.90]) widget = Image(fig) From 2daeb650972de587225393f19cb60fb354d40f04 Mon Sep 17 00:00:00 2001 From: ramyamounir <44840015+ramyamounir@users.noreply.github.com> Date: Fri, 12 Dec 2025 06:09:14 -0500 Subject: [PATCH 13/15] refactor!: label renames for lineplot --- .../plots/interactive_hypothesis_space_pointcloud.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/tbp/plot/plots/interactive_hypothesis_space_pointcloud.py b/src/tbp/plot/plots/interactive_hypothesis_space_pointcloud.py index d0b25ed..f3000d9 100644 --- a/src/tbp/plot/plots/interactive_hypothesis_space_pointcloud.py +++ b/src/tbp/plot/plots/interactive_hypothesis_space_pointcloud.py @@ -1288,13 +1288,19 @@ def _create_burst_figure(self, global_step: int) -> plt.Figure: time_lines: list[Line2D] = [] time_labels: list[str] = [] + label_renames = { + "Max slope": "Max Recent Evidence Growth", + "Hypothesis space size": "Hypothesis Space Size", + "Burst": "Sampling Burst", + "Current step": "Current Step", + } for line, label in zip(all_lines, all_labels, strict=True): if label in ("Max slope", "Hypothesis space size"): data_lines.append(line) - data_labels.append(label) + data_labels.append(label_renames.get(label, label)) elif label in ("Burst", "Current step"): time_lines.append(line) - time_labels.append(label) + time_labels.append(label_renames.get(label, label)) if data_lines: legend_metrics = ax_left.legend( From 54517032c4fbb5b33ac48f38e74383401bf66ed1 Mon Sep 17 00:00:00 2001 From: ramyamounir <44840015+ramyamounir@users.noreply.github.com> Date: Sat, 13 Dec 2025 08:50:12 -0500 Subject: [PATCH 14/15] docs: change docstrings and fix typos --- .../interactive_hypothesis_space_pointcloud.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/tbp/plot/plots/interactive_hypothesis_space_pointcloud.py b/src/tbp/plot/plots/interactive_hypothesis_space_pointcloud.py index f3000d9..49aebf5 100644 --- a/src/tbp/plot/plots/interactive_hypothesis_space_pointcloud.py +++ b/src/tbp/plot/plots/interactive_hypothesis_space_pointcloud.py @@ -1036,9 +1036,9 @@ class LinePlotWidgetOps: """WidgetOps implementation for the line plot. This widget shows a line plot for the max global slope and hypothesis space size - over time. It also shows the sampling bursts locations as vertical dashed red lines. - The current step is shown as a vertical solid black line and moves with the step - slider control. + over time. It also shows the sampling bursts locations as vertical dashed violet + lines. The current step is shown as a vertical solid black line and moves with the + step slider control. """ def __init__( @@ -1242,7 +1242,7 @@ def _create_burst_figure(self, global_step: int) -> plt.Figure: ax_left.set_ylim(-1.0, 2.0) ax_right.set_ylim(0, 10000) - # Burst locations (red dashed lines) + # Burst locations (violet dashed lines) add_idx = np.flatnonzero(bursts) if add_idx.size > 0: ymin, ymax = ax_left.get_ylim() @@ -1272,7 +1272,7 @@ def _create_burst_figure(self, global_step: int) -> plt.Figure: label="Current step", ) - ax_left.set_ylabel("Max slope") + ax_left.set_ylabel("Max Recent Evidence Growth") ax_right.set_ylabel("Hyp space size") # Collect legend entries @@ -1466,8 +1466,8 @@ class InteractivePlot: - Buttons to activate or deactivate the history of agent and/or patch locations. - A plot of the pretrained model for the primary target with a button to show the hypothesis space for this object. - - The hypothesis space pointcloud can be coloured by different metrics, such as - evidence, mlh, slope, pose error or age. + - The color-map of the hypothesis space pointcloud can reflect different metric + values, such as evidence, mlh, slope, pose error or age. - A line plot showing the maximum global slope (burst trigger), the location/duration of the sampling bursts and the hypothesis space size over time. From de62bdd9bea65ce5b8b59e6f91fba8dbe8c23ae6 Mon Sep 17 00:00:00 2001 From: ramyamounir <44840015+ramyamounir@users.noreply.github.com> Date: Sat, 13 Dec 2025 08:50:41 -0500 Subject: [PATCH 15/15] refactor: change current_episode default from -1 to None --- src/tbp/plot/plots/interactive_hypothesis_space_pointcloud.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tbp/plot/plots/interactive_hypothesis_space_pointcloud.py b/src/tbp/plot/plots/interactive_hypothesis_space_pointcloud.py index 49aebf5..ec209c4 100644 --- a/src/tbp/plot/plots/interactive_hypothesis_space_pointcloud.py +++ b/src/tbp/plot/plots/interactive_hypothesis_space_pointcloud.py @@ -225,7 +225,7 @@ def __init__( } self.step_mapper = step_mapper - self.current_episode = -1 + self.current_episode: int | None = None def add(self, callback: Callable) -> Slider2D: kwargs = deepcopy(self._add_kwargs)