diff --git a/FORMATTING.md b/FORMATTING.md deleted file mode 100644 index 299aae8..0000000 --- a/FORMATTING.md +++ /dev/null @@ -1,101 +0,0 @@ -# Python Code Formatting Guide - -This project uses automated Python code formatting to ensure consistent code style across the entire codebase. - -## ๐Ÿš€ Quick Start - -### Option 1: Use the Virtual Environment (Recommended) -```bash -# One-time setup -./setup_venv_formatters.sh - -# Format your code -./format_with_venv.sh - -# Or activate and use manually -source activate_formatters.sh -black . --line-length 88 --target-version py38 -isort . --profile black -flake8 . -``` - -### Option 2: Use Existing Environment -```bash -# If you already have the tools installed -./format.sh -``` - -## ๐Ÿ”ง Tools Used - -- **Black** (24.10.0+): Automatic Python code formatter -- **isort** (6.0.1+): Import statement organizer -- **flake8** (7.2.0+): Code linter and style checker -- **pre-commit** (3.8.0+): Git hooks for automatic formatting - -## ๐Ÿ“ Files in This Setup - -| File | Purpose | -|------|---------| -| `setup_venv_formatters.sh` | Creates virtual environment with formatters | -| `activate_formatters.sh` | Quick activation script | -| `format_with_venv.sh` | Formats code using virtual environment | -| `format.sh` | Legacy formatting script | -| `pyproject.toml` | Tool configuration | -| `.flake8` | Flake8-specific configuration | -| `.pre-commit-config.yaml` | Git hooks configuration | - -## ๐Ÿ”„ CI/CD Integration - -The CI pipeline automatically checks code formatting on every pull request: - -1. **Black formatting check** - Ensures consistent code style -2. **Import sorting check** - Validates import organization -3. **Flake8 linting** - Checks for code quality issues - -If any check fails, the CI will block the PR with clear instructions to run the formatter. - -## ๐Ÿ› ๏ธ Configuration - -### Black Settings -- Line length: 88 characters -- Target Python version: 3.8 -- Profile: Compatible with isort - -### Flake8 Settings -- Ignores common style issues that don't affect functionality -- Focuses on important code quality checks -- Uses same line length as Black - -## ๐Ÿ’ก Usage Tips - -1. **Before committing**: Pre-commit hooks will automatically format your code -2. **If CI fails**: Run `./format_with_venv.sh` to fix all issues -3. **For specific files**: Use tools directly after activating the environment - -## ๐Ÿ” Troubleshooting - -### Virtual Environment Issues -```bash -# If the environment is corrupted, recreate it -rm -rf .venv-formatters -./setup_venv_formatters.sh -``` - -### CI Version Conflicts -The CI uses flexible version ranges to ensure compatibility with the latest available public versions of the formatting tools. - -### Pre-commit Hook Issues -```bash -# Reinstall hooks if needed -source activate_formatters.sh -pre-commit install --overwrite -``` - -## ๐Ÿ“Š Version Compatibility - -This setup is designed to work with the latest publicly available versions: -- Black: `>=24.0.0,<25.0.0` -- isort: `>=5.12.0,<7.0.0` -- flake8: `>=6.0.0,<8.0.0` - -The virtual environment approach ensures consistent versions across all developers and CI environments. diff --git a/README.md b/README.md index 6ffc34f..1e06c41 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@

- Philosophy + Philosophy ยท Autonomy Docs ยท diff --git a/autonomy/.python-version b/autonomy/.python-version new file mode 100644 index 0000000..24ee5b1 --- /dev/null +++ b/autonomy/.python-version @@ -0,0 +1 @@ +3.13 diff --git a/autonomy/CMakeLists.txt b/autonomy/CMakeLists.txt index 5581c17..4995384 100644 --- a/autonomy/CMakeLists.txt +++ b/autonomy/CMakeLists.txt @@ -8,6 +8,7 @@ add_compile_options(-std=c++11) catkin_python_setup() find_package(catkin REQUIRED COMPONENTS + autonomy_interfaces nav_msgs sensor_msgs stereo_msgs @@ -26,25 +27,70 @@ if (CATKIN_ENABLE_TESTING) find_package(rostest REQUIRED) endif() +# --- Begin: Copy shared interface definitions into this package --- +# Location of shared interface source files (relative to this package) +set(SHARED_INTERFACES_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../shared_interfaces") + +# Ensure local interface directories exist +file(MAKE_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/msg") +file(MAKE_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/srv") +file(MAKE_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/action") + +# Gather shared files +file(GLOB SHARED_MSGS "${SHARED_INTERFACES_DIR}/msg/*.msg") +file(GLOB SHARED_SRVS "${SHARED_INTERFACES_DIR}/srv/*.srv") +file(GLOB SHARED_ACTIONS "${SHARED_INTERFACES_DIR}/action/*.action") + +# Copy shared files into this package so catkin can find them +foreach(f ${SHARED_MSGS}) + get_filename_component(_name "${f}" NAME) + configure_file("${f}" "${CMAKE_CURRENT_SOURCE_DIR}/msg/${_name}" COPYONLY) +endforeach() +foreach(f ${SHARED_SRVS}) + get_filename_component(_name "${f}" NAME) + configure_file("${f}" "${CMAKE_CURRENT_SOURCE_DIR}/srv/${_name}" COPYONLY) +endforeach() +foreach(f ${SHARED_ACTIONS}) + get_filename_component(_name "${f}" NAME) + configure_file("${f}" "${CMAKE_CURRENT_SOURCE_DIR}/action/${_name}" COPYONLY) +endforeach() + +# Build lists of filenames relative to package dirs +set(MSG_FILES) +foreach(f ${SHARED_MSGS}) + get_filename_component(_name "${f}" NAME) + list(APPEND MSG_FILES ${_name}) +endforeach() + +set(SRV_FILES) +foreach(f ${SHARED_SRVS}) + get_filename_component(_name "${f}" NAME) + list(APPEND SRV_FILES ${_name}) +endforeach() + +set(ACTION_FILES) +foreach(f ${SHARED_ACTIONS}) + get_filename_component(_name "${f}" NAME) + list(APPEND ACTION_FILES ${_name}) +endforeach() +# --- End: Copy shared interface definitions --- + # Add message files add_message_files( FILES - Detection.msg - Detections.msg + ${MSG_FILES} ) add_action_files( FILES - NavigateToWaypoint.action + ${ACTION_FILES} ) # Add service files add_service_files( FILES - SetColorFilter.srv - SetYoloModel.srv - FireTorpedo.srv + ${SRV_FILES} ) # Generate added messages and services @@ -58,6 +104,7 @@ generate_messages( catkin_package( CATKIN_DEPENDS + autonomy_interfaces nav_msgs sensor_msgs stereo_msgs diff --git a/autonomy/README.md b/autonomy/README.md index 31b780a..733a67c 100644 --- a/autonomy/README.md +++ b/autonomy/README.md @@ -1,5 +1,11 @@ # Hydrus Autonomy System + +This module is going to be deprecated for the following reasons: + + + + The Hydrus Autonomy System is a comprehensive ROS-based underwater vehicle control and mission management framework. This README documents all available scripts, their purpose, and usage instructions. > โš ๏ธ **Warning**: The mission API is currently under development. Some features may not work as expected and are subject to change. diff --git a/autonomy/launch/depth_estimation.launch b/autonomy/launch/depth_estimation.launch deleted file mode 100644 index 5e05225..0000000 --- a/autonomy/launch/depth_estimation.launch +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - diff --git a/autonomy/launch/godot_bridge.launch b/autonomy/launch/godot_bridge.launch deleted file mode 100644 index b91975a..0000000 --- a/autonomy/launch/godot_bridge.launch +++ /dev/null @@ -1,47 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/autonomy/launch/profiler.launch b/autonomy/launch/profiler.launch deleted file mode 100644 index a7677e1..0000000 --- a/autonomy/launch/profiler.launch +++ /dev/null @@ -1,23 +0,0 @@ - - - - - - - - - - - - - - - - - $(arg target_nodes) - - - diff --git a/autonomy/launch/simulation.launch b/autonomy/launch/simulation.launch deleted file mode 100644 index 3e8c19e..0000000 --- a/autonomy/launch/simulation.launch +++ /dev/null @@ -1,37 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/autonomy/main.py b/autonomy/main.py new file mode 100644 index 0000000..428c106 --- /dev/null +++ b/autonomy/main.py @@ -0,0 +1,6 @@ +def main(): + print("Hello from autonomy!") + + +if __name__ == "__main__": + main() diff --git a/autonomy/package.xml b/autonomy/package.xml index 20d4ffe..b503802 100644 --- a/autonomy/package.xml +++ b/autonomy/package.xml @@ -14,6 +14,9 @@ catkin_pkg python-catkin-pkg + + autonomy_interfaces + nav_msgs sensor_msgs diff --git a/autonomy/pyproject.toml b/autonomy/pyproject.toml new file mode 100644 index 0000000..5294fc3 --- /dev/null +++ b/autonomy/pyproject.toml @@ -0,0 +1,16 @@ +[project] +name = "autonomy" +version = "0.1.0" +description = "Add your description here" +readme = "README.md" +requires-python = ">=3.12" +dependencies = [ + "colorama>=0.4.6", + "fastapi>=0.116.1", + "matplotlib>=3.10.5", + "opencv-python>=4.11.0.86", + "pyserial>=3.5", + "requests>=2.32.5", + "termcolor>=3.1.0", + "uvicorn>=0.35.0", +] diff --git a/autonomy/scripts/cv/depth_estimation_node.py b/autonomy/scripts/cv/depth_estimation_node.py deleted file mode 100644 index 463a612..0000000 --- a/autonomy/scripts/cv/depth_estimation_node.py +++ /dev/null @@ -1,135 +0,0 @@ -#!/usr/bin/env python3 -""" -ROS node for depth estimation using Depth-Anything-ONNX -Publishes depth maps and provides depth estimation services -""" - -import os -import sys - -import cv2 -import numpy as np -import rospy -from cv_bridge import CvBridge -from sensor_msgs.msg import CompressedImage, Image -from std_msgs.msg import Float32 - -# Add the computer vision module to path -sys.path.append(os.path.dirname(__file__)) - -try: - from computer_vision.depth_estimation import DepthEstimator - - DEPTH_ESTIMATION_AVAILABLE = True -except ImportError as e: - DEPTH_ESTIMATION_AVAILABLE = False - rospy.logwarn(f"Depth estimation dependencies not available: {e}") - rospy.logwarn("Install with: pip install -e .[depth-estimation]") - - -class DepthEstimationNode: - """ROS node for depth estimation""" - - def __init__(self): - rospy.init_node("depth_estimation_node") - - # Check if depth estimation is available - if not DEPTH_ESTIMATION_AVAILABLE: - rospy.logerr("Depth estimation dependencies not installed!") - rospy.logerr("Install with: pip install -e .[depth-estimation]") - rospy.signal_shutdown("Missing dependencies") - return - - # Parameters - self.encoder = rospy.get_param("~encoder", "vitb") - self.model_path = rospy.get_param("~model_path", None) - self.input_topic = rospy.get_param("~input_topic", "/camera/image_raw") - self.use_compressed = rospy.get_param("~use_compressed", False) - self.publish_colored = rospy.get_param("~publish_colored", True) - - # Initialize components - self.bridge = CvBridge() - - try: - self.depth_estimator = DepthEstimator( - model_path=self.model_path, encoder=self.encoder - ) - except ImportError as e: - rospy.logerr(f"Failed to initialize depth estimator: {e}") - rospy.signal_shutdown("Depth estimator initialization failed") - return - - # Publishers - self.depth_pub = rospy.Publisher("~depth_map", Image, queue_size=1) - self.depth_colored_pub = rospy.Publisher("~depth_colored", Image, queue_size=1) - self.center_depth_pub = rospy.Publisher("~center_depth", Float32, queue_size=1) - - # Subscribers - if self.use_compressed: - self.image_sub = rospy.Subscriber( - self.input_topic, CompressedImage, self.compressed_image_callback - ) - else: - self.image_sub = rospy.Subscriber( - self.input_topic, Image, self.image_callback - ) - - rospy.loginfo(f"Depth estimation node started with encoder: {self.encoder}") - rospy.loginfo(f"Subscribing to: {self.input_topic}") - - def image_callback(self, msg): - """Process uncompressed image messages""" - try: - # Convert ROS image to OpenCV - cv_image = self.bridge.imgmsg_to_cv2(msg, "bgr8") - self.process_image(cv_image, msg.header) - except Exception as e: - rospy.logerr(f"Error processing image: {e}") - - def compressed_image_callback(self, msg): - """Process compressed image messages""" - try: - # Convert compressed image to OpenCV - np_arr = np.frombuffer(msg.data, np.uint8) - cv_image = cv2.imdecode(np_arr, cv2.IMREAD_COLOR) - self.process_image(cv_image, msg.header) - except Exception as e: - rospy.logerr(f"Error processing compressed image: {e}") - - def process_image(self, cv_image, header): - """Process image and publish depth information""" - try: - # Estimate depth - raw_depth, viz_depth = self.depth_estimator.estimate_depth(cv_image) - - # Publish raw depth map - depth_msg = self.bridge.cv2_to_imgmsg(raw_depth.astype(np.float32), "32FC1") - depth_msg.header = header - self.depth_pub.publish(depth_msg) - - # Publish colored depth map if enabled - if self.publish_colored: - colored_depth = self.depth_estimator.create_colored_depth_map(viz_depth) - colored_msg = self.bridge.cv2_to_imgmsg(colored_depth, "bgr8") - colored_msg.header = header - self.depth_colored_pub.publish(colored_msg) - - # Publish center depth value - h, w = cv_image.shape[:2] - center_depth = self.depth_estimator.get_depth_at_point( - raw_depth, w // 2, h // 2 - ) - center_msg = Float32() - center_msg.data = center_depth - self.center_depth_pub.publish(center_msg) - - except Exception as e: - rospy.logerr(f"Error in depth processing: {e}") - - -if __name__ == "__main__": - try: - node = DepthEstimationNode() - rospy.spin() - except rospy.ROSInterruptException: - pass diff --git a/autonomy/scripts/profiler/profiling_decorators.py b/autonomy/scripts/profiler/profiling_decorators.py deleted file mode 100644 index a155adb..0000000 --- a/autonomy/scripts/profiler/profiling_decorators.py +++ /dev/null @@ -1,276 +0,0 @@ -#!/usr/bin/env python3 -""" -Function Profiling Decorators - -Decorators and utilities for profiling function execution times in ROS nodes. -Use these to instrument critical functions for detailed performance analysis. - -Usage: - from autonomy.scripts.services.profiling_decorators import profile_function, ProfileManager - - # Decorate functions you want to profile - @profile_function("cv_processing") - def process_image(self, image): - # Your function code here - pass - - # Get profiling results - ProfileManager.get_stats() -""" - -import functools -import json -import threading -import time -from collections import defaultdict, deque -from typing import Any, Callable, Dict, List, Optional - - -class ProfileManager: - """Global manager for function profiling data""" - - _instance = None - _lock = threading.Lock() - - def __new__(cls): - if cls._instance is None: - with cls._lock: - if cls._instance is None: - cls._instance = super().__new__(cls) - cls._instance._initialized = False - return cls._instance - - def __init__(self): - if self._initialized: - return - - self.function_stats = defaultdict( - lambda: { - "call_count": 0, - "total_time": 0.0, - "min_time": float("inf"), - "max_time": 0.0, - "recent_times": deque(maxlen=100), # Last 100 calls - "avg_time": 0.0, - "calls_per_second": 0.0, - "last_call": 0.0, - } - ) - self.node_name = "unknown" - self._initialized = True - - def set_node_name(self, name: str): - """Set the name of the current node""" - self.node_name = name - - def record_function_call(self, function_name: str, execution_time: float): - """Record a function call and its execution time""" - with self._lock: - stats = self.function_stats[function_name] - current_time = time.time() - - # Update basic stats - stats["call_count"] += 1 - stats["total_time"] += execution_time - stats["min_time"] = min(stats["min_time"], execution_time) - stats["max_time"] = max(stats["max_time"], execution_time) - stats["recent_times"].append(execution_time) - stats["last_call"] = current_time - - # Calculate averages - stats["avg_time"] = stats["total_time"] / stats["call_count"] - - # Calculate calls per second (based on recent calls) - if len(stats["recent_times"]) > 1: - time_span = current_time - (current_time - len(stats["recent_times"])) - if time_span > 0: - stats["calls_per_second"] = len(stats["recent_times"]) / time_span - - def get_stats(self) -> Dict[str, Any]: - """Get current profiling statistics""" - with self._lock: - return {"node_name": self.node_name, "functions": dict(self.function_stats)} - - def get_top_functions( - self, metric: str = "total_time", limit: int = 10 - ) -> List[tuple]: - """Get top functions by specified metric""" - with self._lock: - functions = [] - for func_name, stats in self.function_stats.items(): - if metric in stats: - functions.append((func_name, stats[metric])) - - functions.sort(key=lambda x: x[1], reverse=True) - return functions[:limit] - - def reset_stats(self): - """Reset all profiling statistics""" - with self._lock: - self.function_stats.clear() - - @classmethod - def get_instance(cls): - """Get the singleton instance""" - return cls() - - -def profile_function(name: Optional[str] = None, category: str = "general"): - """ - Decorator to profile function execution time - - Args: - name: Custom name for the function (defaults to function name) - category: Category for grouping related functions - """ - - def decorator(func: Callable) -> Callable: - function_name = name or f"{category}.{func.__name__}" - - @functools.wraps(func) - def wrapper(*args, **kwargs): - start_time = time.perf_counter() - try: - result = func(*args, **kwargs) - return result - finally: - end_time = time.perf_counter() - execution_time = end_time - start_time - ProfileManager.get_instance().record_function_call( - function_name, execution_time - ) - - return wrapper - - return decorator - - -def profile_method(name: Optional[str] = None, category: str = "methods"): - """ - Decorator specifically for class methods - - Args: - name: Custom name for the method (defaults to class.method) - category: Category for grouping related methods - """ - - def decorator(func: Callable) -> Callable: - @functools.wraps(func) - def wrapper(self, *args, **kwargs): - if name: - function_name = name - else: - class_name = self.__class__.__name__ - function_name = f"{category}.{class_name}.{func.__name__}" - - start_time = time.perf_counter() - try: - result = func(self, *args, **kwargs) - return result - finally: - end_time = time.perf_counter() - execution_time = end_time - start_time - ProfileManager.get_instance().record_function_call( - function_name, execution_time - ) - - return wrapper - - return decorator - - -class FunctionProfiler: - """Context manager for profiling code blocks""" - - def __init__(self, name: str): - self.name = name - self.start_time = None - - def __enter__(self): - self.start_time = time.perf_counter() - return self - - def __exit__(self, exc_type, exc_val, exc_tb): - if self.start_time: - execution_time = time.perf_counter() - self.start_time - ProfileManager.get_instance().record_function_call( - self.name, execution_time - ) - - -def profile_ros_callback(topic_name: str): - """ - Decorator specifically for ROS callback functions - - Args: - topic_name: Name of the ROS topic for identification - """ - - def decorator(func: Callable) -> Callable: - function_name = f"ros_callback.{topic_name}.{func.__name__}" - - @functools.wraps(func) - def wrapper(*args, **kwargs): - start_time = time.perf_counter() - try: - result = func(*args, **kwargs) - return result - finally: - end_time = time.perf_counter() - execution_time = end_time - start_time - ProfileManager.get_instance().record_function_call( - function_name, execution_time - ) - - return wrapper - - return decorator - - -def export_profiling_data(filename: str): - """Export profiling data to JSON file""" - stats = ProfileManager.get_instance().get_stats() - - # Convert deque objects to lists for JSON serialization - for func_stats in stats["functions"].values(): - if "recent_times" in func_stats: - func_stats["recent_times"] = list(func_stats["recent_times"]) - - with open(filename, "w") as f: - json.dump(stats, f, indent=2, default=str) - - -# Example usage and testing -if __name__ == "__main__": - # Example of how to use the profiling decorators - - @profile_function("test_function", "testing") - def slow_function(): - time.sleep(0.1) - return "done" - - @profile_method("test_method", "testing") - class TestClass: - def slow_method(self): - time.sleep(0.05) - return "method done" - - # Test the profiling - ProfileManager.get_instance().set_node_name("test_node") - - # Call functions multiple times - for i in range(5): - slow_function() - - test_obj = TestClass() - for i in range(3): - test_obj.slow_method() - - # Use context manager - with FunctionProfiler("manual_timing"): - time.sleep(0.02) - - # Print results - stats = ProfileManager.get_instance().get_stats() - print("Profiling Results:") - print(json.dumps(stats, indent=2, default=str)) diff --git a/autonomy/scripts/profiler/profiling_test.py b/autonomy/scripts/profiler/profiling_test.py deleted file mode 100644 index 60a9bc8..0000000 --- a/autonomy/scripts/profiler/profiling_test.py +++ /dev/null @@ -1,114 +0,0 @@ -#!/usr/bin/env python3 -""" -Simple test of the profiling decorators without ROS dependencies -This allows testing the profiling functionality independently -""" - -import os -import sys -import time - -import numpy as np - -# Add the current directory to path -sys.path.append(os.path.dirname(__file__)) - -from profiling_decorators import ( - FunctionProfiler, - ProfileManager, - export_profiling_data, - profile_function, - profile_method, -) - -# Initialize profiling -ProfileManager.get_instance().set_node_name("profiling_test") - - -@profile_function("image_processing", "cv") -def process_image(image): - """Mock image processing function""" - with FunctionProfiler("cv.blur"): - # Simulate Gaussian blur - time.sleep(0.01) # Simulate processing time - - with FunctionProfiler("cv.threshold"): - # Simulate thresholding - time.sleep(0.005) - - return True - - -@profile_function("detection", "cv") -def detect_objects(image): - """Mock object detection""" - with FunctionProfiler("cv.inference"): - time.sleep(0.02) # Simulate model inference - - with FunctionProfiler("cv.postprocess"): - time.sleep(0.003) # Simulate post-processing - - return [{"bbox": [100, 100, 50, 50], "confidence": 0.8}] - - -class MockProcessor: - @profile_method("process_frame", "pipeline") - def process_frame(self, frame): - """Mock frame processing pipeline""" - process_image(frame) - detections = detect_objects(frame) - return detections - - -def main(): - print("๐Ÿ”ง Testing Profiling Decorators") - print("=" * 40) - - # Create mock data - mock_image = np.zeros((480, 640, 3), dtype=np.uint8) - processor = MockProcessor() - - # Run multiple iterations to collect data - print("Running profiling test iterations...") - for i in range(20): - processor.process_frame(mock_image) - if i % 5 == 0: - print(f"Completed {i+1}/20 iterations") - - # Get and display results - stats = ProfileManager.get_instance().get_stats() - print(f"\n๐Ÿ“ˆ Profiling Results:") - print(f"Node: {stats['node_name']}") - print(f"Functions profiled: {len(stats['functions'])}") - print() - - # Sort functions by total time - sorted_functions = sorted( - stats["functions"].items(), key=lambda x: x[1]["total_time"], reverse=True - ) - - print("Function Performance (sorted by total time):") - print("-" * 60) - print(f"{'Function':<25} {'Calls':<8} {'Avg (ms)':<10} {'Total (ms)':<12}") - print("-" * 60) - - for func_name, func_stats in sorted_functions: - avg_time = func_stats["avg_time"] * 1000 - total_time = func_stats["total_time"] * 1000 - call_count = func_stats["call_count"] - print(f"{func_name:<25} {call_count:<8} {avg_time:<10.2f} {total_time:<12.2f}") - - # Export data - export_filename = "profiling_test_results.json" - export_profiling_data(export_filename) - print(f"\nโœ… Detailed results exported to {export_filename}") - - # Show top slowest functions - print(f"\n๐ŸŒ Top 3 slowest functions (by average time):") - top_slow = ProfileManager.get_instance().get_top_functions("avg_time", 3) - for i, (func_name, avg_time) in enumerate(top_slow, 1): - print(f" {i}. {func_name}: {avg_time*1000:.2f}ms avg") - - -if __name__ == "__main__": - main() diff --git a/autonomy/src/cv_publishers.py b/autonomy/src/cv_publishers.py index 913734d..463f1df 100755 --- a/autonomy/src/cv_publishers.py +++ b/autonomy/src/cv_publishers.py @@ -8,15 +8,14 @@ from typing import List, Optional, Tuple -import custom_types import numpy as np import rospy from geometry_msgs.msg import Point, PoseStamped from sensor_msgs.msg import CameraInfo, Image, RegionOfInterest from visualization_msgs.msg import Marker, MarkerArray -from autonomy.msg import Detection, Detections -from autonomy.srv import ( +from autonomy_interfaces.msg import Detection, Detections +from autonomy_interfaces.srv import ( SetColorFilter, SetColorFilterResponse, SetYoloModel, @@ -25,6 +24,7 @@ # Import our custom detection core module from computer_vision.detection_core import ColorFilterConfig, DetectionPipelineManager +from computer_vision import custom_types ############################ # Global variables diff --git a/autonomy2/CMakeLists.txt b/autonomy2/CMakeLists.txt new file mode 100644 index 0000000..f41b40b --- /dev/null +++ b/autonomy2/CMakeLists.txt @@ -0,0 +1,71 @@ +cmake_minimum_required(VERSION 3.8) +project(autonomy) + +if(NOT CMAKE_CXX_STANDARD) + set(CMAKE_CXX_STANDARD 17) +endif() + +find_package(ament_cmake REQUIRED) +find_package(rclcpp REQUIRED) +find_package(sensor_msgs REQUIRED) +find_package(geometry_msgs REQUIRED) +find_package(rosidl_default_generators REQUIRED) + +# --- Begin: Copy shared interface definitions into this package --- +# Location of shared interface source files (relative to this package) +set(SHARED_INTERFACES_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../shared_interfaces") + +# Ensure local interface directories exist +file(MAKE_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/msg") +file(MAKE_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/srv") +file(MAKE_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/action") + +# Gather shared files +file(GLOB SHARED_MSGS "${SHARED_INTERFACES_DIR}/msg/*.msg") +file(GLOB SHARED_SRVS "${SHARED_INTERFACES_DIR}/srv/*.srv") +file(GLOB SHARED_ACTIONS "${SHARED_INTERFACES_DIR}/action/*.action") + +# Copy shared files into this package so rosidl can find them +foreach(f ${SHARED_MSGS}) + get_filename_component(_name "${f}" NAME) + configure_file("${f}" "${CMAKE_CURRENT_SOURCE_DIR}/msg/${_name}" COPYONLY) +endforeach() +foreach(f ${SHARED_SRVS}) + get_filename_component(_name "${f}" NAME) + configure_file("${f}" "${CMAKE_CURRENT_SOURCE_DIR}/srv/${_name}" COPYONLY) +endforeach() +foreach(f ${SHARED_ACTIONS}) + get_filename_component(_name "${f}" NAME) + configure_file("${f}" "${CMAKE_CURRENT_SOURCE_DIR}/action/${_name}" COPYONLY) +endforeach() + +# Build lists of filenames relative to package dirs +set(MSG_FILES) +foreach(f ${SHARED_MSGS}) + get_filename_component(_name "${f}" NAME) + list(APPEND MSG_FILES "msg/${_name}") +endforeach() + +set(SRV_FILES) +foreach(f ${SHARED_SRVS}) + get_filename_component(_name "${f}" NAME) + list(APPEND SRV_FILES "srv/${_name}") +endforeach() + +set(ACTION_FILES) +foreach(f ${SHARED_ACTIONS}) + get_filename_component(_name "${f}" NAME) + list(APPEND ACTION_FILES "action/${_name}") +endforeach() +# --- End: Copy shared interface definitions --- + +rosidl_generate_interfaces(${PROJECT_NAME} + ${MSG_FILES} + ${SRV_FILES} + ${ACTION_FILES} + DEPENDENCIES geometry_msgs sensor_msgs +) + +ament_export_dependencies(rosidl_default_runtime) + +ament_package() diff --git a/autonomy2/README.md b/autonomy2/README.md new file mode 100644 index 0000000..13d1a08 --- /dev/null +++ b/autonomy2/README.md @@ -0,0 +1,9 @@ +# ROS 2 messages for Hydrus autonomy + +This package defines ROS 2 versions of: +- `autonomy/Detection` +- `autonomy/Detections` + +They mirror the fields from the ROS 1 messages. + +Build with colcon from the workspace root. diff --git a/autonomy2/colcon.pkg b/autonomy2/colcon.pkg new file mode 100644 index 0000000..cd1d2a9 --- /dev/null +++ b/autonomy2/colcon.pkg @@ -0,0 +1 @@ +{ "name": "autonomy" } diff --git a/autonomy2/src/cv_node_py/CMakeLists.txt b/autonomy2/src/cv_node_py/CMakeLists.txt new file mode 100644 index 0000000..755f811 --- /dev/null +++ b/autonomy2/src/cv_node_py/CMakeLists.txt @@ -0,0 +1,5 @@ +cmake_minimum_required(VERSION 3.8) +project(cv_node_py) + +find_package(ament_cmake REQUIRED) +ament_package() diff --git a/autonomy2/src/cv_node_py/README.md b/autonomy2/src/cv_node_py/README.md new file mode 100644 index 0000000..23b859e --- /dev/null +++ b/autonomy2/src/cv_node_py/README.md @@ -0,0 +1,12 @@ +# cv_node_py + +ROS 2 Python node wrapping existing `src/computer_vision/detection_core.py`. + +## Build + +```bash +# In workspace root (ros2_ws) +colcon build --symlink-install +. install/setup.bash +ros2 launch cv_node_py cv_node.launch.py +``` diff --git a/autonomy2/src/cv_node_py/colcon.pkg b/autonomy2/src/cv_node_py/colcon.pkg new file mode 100644 index 0000000..a06ee9d --- /dev/null +++ b/autonomy2/src/cv_node_py/colcon.pkg @@ -0,0 +1 @@ +{ "name": "cv_node_py" } diff --git a/scripts/commands/test/test_cache.py b/autonomy2/src/cv_node_py/cv_node_py/__init__.py similarity index 100% rename from scripts/commands/test/test_cache.py rename to autonomy2/src/cv_node_py/cv_node_py/__init__.py diff --git a/autonomy2/src/cv_node_py/cv_node_py/node.py b/autonomy2/src/cv_node_py/cv_node_py/node.py new file mode 100644 index 0000000..89789a5 --- /dev/null +++ b/autonomy2/src/cv_node_py/cv_node_py/node.py @@ -0,0 +1,116 @@ +#!/usr/bin/env python3 +import os + +# Reuse existing detection core utilities +import sys +from typing import List, Tuple + +import rclpy +from geometry_msgs.msg import Point +from rclpy.node import Node +from rclpy.qos import qos_profile_sensor_data +from sensor_msgs.msg import Image, RegionOfInterest + +sys.path.append( + os.path.abspath( + os.path.join(os.path.dirname(__file__), "../../../../src/computer_vision") + ) +) +from detection_core import ColorFilterConfig, DetectionPipelineManager, ros_img_to_cv2 + +from autonomy.msg import Detection as DetectionMsg +from autonomy.msg import Detections as DetectionsMsg + + +class CvNode(Node): + def __init__(self): + super().__init__("cv_node") + # Parameters + self.declare_parameter("yolo_model", "yolo11n.pt") + self.declare_parameter("color_target_rgb", [255, 0, 0]) + self.declare_parameter("color_tolerance", 0.4) + self.declare_parameter("color_min_confidence", 0.3) + self.declare_parameter("color_min_area", 0.2) + self.declare_parameter("image_topic", "/camera/image_raw") + self.declare_parameter("detections_topic", "/detections") + + # Build pipeline + self.pipeline = DetectionPipelineManager() + model = self.get_parameter("yolo_model").get_parameter_value().string_value + try: + self.pipeline.load_yolo_model(model) + except Exception as e: + self.get_logger().warn(f"YOLO model load failed: {e}") + + rgb = ( + self.get_parameter("color_target_rgb") + .get_parameter_value() + .integer_array_value + ) + tol = float(self.get_parameter("color_tolerance").value) + min_conf = float(self.get_parameter("color_min_confidence").value) + min_area = float(self.get_parameter("color_min_area").value) + self.pipeline.update_color_filter_config( + ColorFilterConfig( + tolerance=tol, + min_confidence=min_conf, + min_area=min_area, + rgb_range=tuple(int(x) for x in rgb), + ) + ) + + # Pub/Sub + image_topic = self.get_parameter("image_topic").value + self.sub = self.create_subscription( + Image, image_topic, self.on_image, qos_profile_sensor_data + ) + det_topic = self.get_parameter("detections_topic").value + self.pub = self.create_publisher(DetectionsMsg, det_topic, 10) + + self.get_logger().info( + f"cv_node started. Subscribing to {image_topic}, publishing {det_topic}" + ) + + def on_image(self, msg: Image): + try: + img = ros_img_to_cv2(msg, encoding=msg.encoding if msg.encoding else "bgr8") + except Exception: + # fallback assume bgr8 + img = ros_img_to_cv2(msg, encoding="bgr8") + + results: List[Tuple[str, list]] = self.pipeline.run_detections(img) + for name, detections in results: + out = DetectionsMsg() + out.detector_name = name + out.detections = [] + for d in detections: + dm = DetectionMsg() + dm.cls = int(d.cls) + dm.confidence = float(d.conf) + # point may be None + if getattr(d, "point", None) is not None: + dm.point = Point( + x=float(d.point.x), y=float(d.point.y), z=float(d.point.z) + ) + else: + dm.point = Point() + roi = RegionOfInterest() + roi.x_offset = int(d.x1) + roi.y_offset = int(d.y1) + roi.width = int(d.x2 - d.x1) + roi.height = int(d.y2 - d.y1) + dm.bounding_box = roi + out.detections.append(dm) + self.pub.publish(out) + + +def main(): + rclpy.init() + node = CvNode() + rclpy.spin(node) + node.destroy_node() + rclpy.shutdown() + + +if __name__ == "__main__": + main() diff --git a/autonomy2/src/cv_node_py/launch/cv_node.launch.py b/autonomy2/src/cv_node_py/launch/cv_node.launch.py new file mode 100644 index 0000000..6b291aa --- /dev/null +++ b/autonomy2/src/cv_node_py/launch/cv_node.launch.py @@ -0,0 +1,24 @@ +from launch import LaunchDescription +from launch_ros.actions import Node + + +def generate_launch_description(): + return LaunchDescription( + [ + Node( + package="cv_node_py", + executable="cv_node", + name="cv_node", + output="screen", + parameters=[ + {"yolo_model": "yolo11n.pt"}, + {"color_target_rgb": [255, 0, 0]}, + {"color_tolerance": 0.4}, + {"color_min_confidence": 0.3}, + {"color_min_area": 0.2}, + {"image_topic": "/camera/image_raw"}, + {"detections_topic": "/detections"}, + ], + ) + ] + ) diff --git a/autonomy2/src/cv_node_py/pyproject.toml b/autonomy2/src/cv_node_py/pyproject.toml new file mode 100644 index 0000000..9787c3b --- /dev/null +++ b/autonomy2/src/cv_node_py/pyproject.toml @@ -0,0 +1,3 @@ +[build-system] +requires = ["setuptools", "wheel"] +build-backend = "setuptools.build_meta" diff --git a/autonomy2/src/cv_node_py/setup.cfg b/autonomy2/src/cv_node_py/setup.cfg new file mode 100644 index 0000000..6c72b4f --- /dev/null +++ b/autonomy2/src/cv_node_py/setup.cfg @@ -0,0 +1,4 @@ +[develop] +script-dir=$base/lib/cv_node_py +[install] +install-scripts=$base/lib/cv_node_py diff --git a/autonomy2/src/cv_node_py/setup.py b/autonomy2/src/cv_node_py/setup.py new file mode 100644 index 0000000..9cd3130 --- /dev/null +++ b/autonomy2/src/cv_node_py/setup.py @@ -0,0 +1,26 @@ +from setuptools import setup + +package_name = "cv_node_py" + +setup( + name=package_name, + version="0.1.0", + packages=[package_name], + data_files=[ + ("share/ament_index/resource_index/packages", ["resource/" + package_name]), + ("share/" + package_name, ["package.xml"]), + ("share/" + package_name + "/launch", ["launch/cv_node.launch.py"]), + ], + install_requires=["setuptools"], + zip_safe=True, + maintainer="Your Name", + maintainer_email="you@example.com", + description="ROS2 Python CV node using detection_core.py", + license="MIT", + tests_require=["pytest"], + entry_points={ + "console_scripts": [ + "cv_node = cv_node_py.node:main", + ], + }, +) diff --git a/autonomy_interfaces/CMakeLists.txt b/autonomy_interfaces/CMakeLists.txt new file mode 100644 index 0000000..0c07b4f --- /dev/null +++ b/autonomy_interfaces/CMakeLists.txt @@ -0,0 +1,54 @@ +cmake_minimum_required(VERSION 3.0.2) +project(autonomy_interfaces) + +## Compile as C++11, supported in ROS Kinetic and newer +add_compile_options(-std=c++11) + +## Find catkin macros and libraries +find_package(catkin REQUIRED COMPONENTS + std_msgs + geometry_msgs + sensor_msgs + actionlib_msgs + message_generation +) + +## Generate messages in the 'msg' folder +add_message_files( + FILES + Detection.msg + Detections.msg +) + +## Generate services in the 'srv' folder +add_service_files( + FILES + FireTorpedo.srv + SetColorFilter.srv + SetYoloModel.srv +) + +## Generate actions in the 'action' folder +add_action_files( + FILES + NavigateToWaypoint.action +) + +## Generate added messages and services with any dependencies listed here +generate_messages( + DEPENDENCIES + std_msgs + geometry_msgs + sensor_msgs + actionlib_msgs +) + +## Declare a catkin package +catkin_package( + CATKIN_DEPENDS + std_msgs + geometry_msgs + sensor_msgs + actionlib_msgs + message_runtime +) diff --git a/autonomy_interfaces/README.md b/autonomy_interfaces/README.md new file mode 100644 index 0000000..cdeda1e --- /dev/null +++ b/autonomy_interfaces/README.md @@ -0,0 +1,14 @@ +# Shared Interfaces (ROS 1 and ROS 2) + +This folder contains the single source of truth for Hydrus message, service, and action interface definitions. + +Structure: +- `msg/` โ€” `.msg` message files +- `srv/` โ€” `.srv` service files +- `action/` โ€” `.action` action files + +Both ROS 1 (`autonomy_ros`) and ROS 2 (`autonomy_ros2`) packages copy these files at CMake configure-time into their local `msg/`, `srv/`, and `action/` folders and generate code from them. Edit files here only, then rebuild the ROS 1/ROS 2 workspaces. + +To add a new interface: +1. Add the `.msg` / `.srv` / `.action` file in this folder hierarchy. +2. Rebuild your ROS 1 and/or ROS 2 workspaces as usual. diff --git a/autonomy/action/NavigateToWaypoint.action b/autonomy_interfaces/action/NavigateToWaypoint.action similarity index 100% rename from autonomy/action/NavigateToWaypoint.action rename to autonomy_interfaces/action/NavigateToWaypoint.action diff --git a/autonomy/msg/Detection.msg b/autonomy_interfaces/msg/Detection.msg similarity index 100% rename from autonomy/msg/Detection.msg rename to autonomy_interfaces/msg/Detection.msg diff --git a/autonomy/msg/Detections.msg b/autonomy_interfaces/msg/Detections.msg similarity index 100% rename from autonomy/msg/Detections.msg rename to autonomy_interfaces/msg/Detections.msg diff --git a/autonomy_interfaces/package.xml b/autonomy_interfaces/package.xml new file mode 100644 index 0000000..f842779 --- /dev/null +++ b/autonomy_interfaces/package.xml @@ -0,0 +1,29 @@ + + + autonomy_interfaces + 0.1.0 + Custom ROS messages, services, and actions for the Hydrus autonomy system + + Hydrus Team + MIT + + catkin + + + message_generation + message_runtime + + + std_msgs + geometry_msgs + sensor_msgs + actionlib_msgs + + std_msgs + geometry_msgs + sensor_msgs + actionlib_msgs + + + + diff --git a/autonomy/srv/FireTorpedo.srv b/autonomy_interfaces/srv/FireTorpedo.srv similarity index 100% rename from autonomy/srv/FireTorpedo.srv rename to autonomy_interfaces/srv/FireTorpedo.srv diff --git a/autonomy/srv/SetColorFilter.srv b/autonomy_interfaces/srv/SetColorFilter.srv similarity index 100% rename from autonomy/srv/SetColorFilter.srv rename to autonomy_interfaces/srv/SetColorFilter.srv diff --git a/autonomy/srv/SetYoloModel.srv b/autonomy_interfaces/srv/SetYoloModel.srv similarity index 100% rename from autonomy/srv/SetYoloModel.srv rename to autonomy_interfaces/srv/SetYoloModel.srv diff --git a/docker/amd64/cuda/camera.Dockerfile b/docker/amd64/camera.Dockerfile similarity index 100% rename from docker/amd64/cuda/camera.Dockerfile rename to docker/amd64/camera.Dockerfile diff --git a/docker/amd64/cpu/hydrus.Dockerfile b/docker/amd64/cpu/hydrus.Dockerfile deleted file mode 100644 index ac7e556..0000000 --- a/docker/amd64/cpu/hydrus.Dockerfile +++ /dev/null @@ -1,109 +0,0 @@ -# Use Ubuntu 20.04 as base image -FROM ubuntu:20.04 - -ARG DEBIAN_FRONTEND=noninteractive -# Update package list -RUN apt-get update && apt-get install -y lsb-release gnupg curl software-properties-common - -# Setup ROS repositories -RUN sh -c 'echo "deb http://packages.ros.org/ros/ubuntu $(lsb_release -sc) main" > /etc/apt/sources.list.d/ros-latest.list' -RUN curl -s https://raw.githubusercontent.com/ros/rosdistro/master/ros.asc | apt-key add - - -# Install ROS Noetic base -RUN apt-get update && apt-get install -y ros-noetic-ros-base - -# Add the deadsnakes PPA and install Python 3.8 -RUN add-apt-repository -y ppa:deadsnakes/ppa && \ - apt-get update && \ - apt-get install -y python3.8 python3.8-distutils python3.8-venv - -# Set Python 3.8 as the default -RUN update-alternatives --install /usr/bin/python3 python3 /usr/bin/python3.8 1 - -# Install pip for Python 3.8 -RUN curl -sS https://bootstrap.pypa.io/pip/3.8/get-pip.py | python3.8 - -#Add kisak-mesa PPA (for latest graphics drivers) -RUN add-apt-repository -y ppa:kisak/kisak-mesa - -# Camera and Computer Vision Dependencies Python-3 -RUN apt-get update && apt-get install -y \ - python3-pip \ - python3-numpy\ - python3-opencv \ - libgl1-mesa-glx \ - ros-noetic-cv-bridge \ - ros-noetic-vision-opencv\ - libbullet-dev \ - python3-empy - - -RUN apt-get update && apt-get install -y\ - ros-noetic-tf2-geometry-msgs\ - python3-tf2-kdl - -RUN sudo sh -c \ - 'echo "deb http://packages.osrfoundation.org/gazebo/ubuntu-stable `lsb_release -cs` main" > /etc/apt/sources.list.d/gazebo-stable.list' &&\ - curl https://packages.osrfoundation.org/gazebo.key | sudo apt-key add - &&\ - apt-get update && apt-get install -y ignition-fortress ros-noetic-ros-ign &&\ - echo "export GZ_VERSION=fortress" >> /root/.bashrc - - -# Embedded Node Dependencies -RUN apt-get install -y --no-install-recommends \ - gcc \ - curl \ - git - -# ROS setup -RUN /bin/bash -c 'source /opt/ros/noetic/setup.bash && \ - mkdir -p /home/catkin_ws/src && \ - cd /home/catkin_ws/ && \ - catkin_make' -RUN echo "source /opt/ros/noetic/setup.bash" >> /root/.bashrc - -# Install Arduino CLI and libraries -WORKDIR /usr/local/ -RUN curl -fsSL https://raw.githubusercontent.com/arduino/arduino-cli/master/install.sh | sh && \ - arduino-cli core update-index && \ - arduino-cli core install arduino:avr -RUN arduino-cli lib install "Servo@1.2.1" && \ - arduino-cli lib install "BlueRobotics MS5837 Library@1.1.1" - - -# Copy embedded Arduino code in the Arduino libraries folder - -# Copy dvl embedded driver -COPY ./devices/DVL/Wayfinder /opt/Wayfinder -WORKDIR /opt/Wayfinder - -# Ultralytics with NO GPU -RUN python3 -m pip install --extra-index-url https://download.pytorch.org/whl/cpu ultralytics -# Copy the Python Dependencies and Install them -COPY ./requirements.txt /requirements.txt -RUN python3 -m pip install --ignore-installed -r /requirements.txt - -# Install Default models for YOLO -RUN echo "export MESA_GL_VERSION_OVERRIDE=3.3" >> /root/.bashrc - -# Install additional dependencies for the embedded node -# Install tmux, vim, git, and htop in a single RUN command -RUN apt-get update && apt-get install -y tmux vim git htop socat - -RUN apt-get update && apt-get install -y \ - ros-noetic-rviz \ - ros-noetic-rqt \ - ros-noetic-rosbag \ - ros-noetic-image-view \ - ros-noetic-tf \ - ros-noetic-tf2-ros \ - ros-noetic-image-transport \ - ros-noetic-laser-proc - -COPY ./devices/Arduino/HydrusModule /root/Arduino/libraries/HydrusModule - -COPY ./ /catkin_ws/src/hydrus-software-stack -WORKDIR /catkin_ws/src/hydrus-software-stack - - -CMD ["/bin/bash", "-c", "sleep infinity"] diff --git a/docker/amd64/cuda/hydrus.Dockerfile b/docker/amd64/cuda/hydrus.Dockerfile deleted file mode 100644 index 3d4cf0d..0000000 --- a/docker/amd64/cuda/hydrus.Dockerfile +++ /dev/null @@ -1,100 +0,0 @@ -# Use Ubuntu 20.04 as base image -FROM ubuntu:20.04 - -# Update package list -ARG DEBIAN_FRONTEND=noninteractive -RUN apt-get update && apt-get install -y lsb-release gnupg curl software-properties-common - -# Setup ROS repositories -RUN sh -c 'echo "deb http://packages.ros.org/ros/ubuntu $(lsb_release -sc) main" > /etc/apt/sources.list.d/ros-latest.list' -RUN curl -s https://raw.githubusercontent.com/ros/rosdistro/master/ros.asc | apt-key add - - -# Install ROS Noetic base and catkin build tools -RUN apt-get update && apt-get install -y ros-noetic-ros-base python3-catkin-tools ros-noetic-catkin - -# Add the deadsnakes PPA and install Python 3.8 -RUN add-apt-repository -y ppa:deadsnakes/ppa && \ - apt-get update && \ - apt-get install -y python3.8 python3.8-distutils python3.8-venv - -# Set Python 3.8 as the default -RUN update-alternatives --install /usr/bin/python3 python3 /usr/bin/python3.8 1 - -# Install pip for Python 3.8 -RUN curl -sS https://bootstrap.pypa.io/pip/3.8/get-pip.py | python3.8 - -#Add kisak-mesa PPA (for latest graphics drivers) -RUN add-apt-repository -y ppa:kisak/kisak-mesa - - -# Camera and Computer Vision Dependencies Python-3 -RUN apt-get update && apt-get install -y \ - python3-pip \ - python3-numpy\ - python3-opencv \ - libgl1-mesa-glx \ - ros-noetic-cv-bridge \ - ros-noetic-vision-opencv\ - libbullet-dev \ - python3-empy - - -RUN apt-get update && apt-get install -y\ - ros-noetic-tf2-geometry-msgs\ - python3-tf2-kdl - - -RUN sudo sh -c \ - 'echo "deb http://packages.osrfoundation.org/gazebo/ubuntu-stable `lsb_release -cs` main" > /etc/apt/sources.list.d/gazebo-stable.list' &&\ - curl https://packages.osrfoundation.org/gazebo.key | sudo apt-key add - &&\ - apt-get update && apt-get install -y ignition-fortress ros-noetic-ros-ign &&\ - echo "export GZ_VERSION=fortress" >> /root/.bashrc - - -# Embedded Node Dependencies -RUN apt-get install -y --no-install-recommends \ - gcc \ - curl \ - git - -# ROS setup -RUN /bin/bash -c 'source /opt/ros/noetic/setup.bash && \ - mkdir -p /home/catkin_ws/src && \ - cd /home/catkin_ws/ && \ - catkin_make' -RUN echo "source /opt/ros/noetic/setup.bash" >> /root/.bashrc - -# Install Arduino CLI and libraries -WORKDIR /usr/local/ -RUN curl -fsSL https://raw.githubusercontent.com/arduino/arduino-cli/master/install.sh | sh && \ - arduino-cli core update-index && \ - arduino-cli core install arduino:avr &&\ - arduino-cli lib install "Servo@1.2.1" && \ - arduino-cli lib install "BlueRobotics MS5837 Library@1.1.1" - - -# Copy the Python Dependencies and Install them -COPY ./requirements.txt /requirements.txt - -# Ultralytics with GPU -RUN python3 -m pip install ultralytics -RUN python3 -m pip install -r /requirements.txt - -# Install Default models for YOLO -RUN curl -Lo /yolov8n.pt https://github.com/ultralytics/assets/releases/latest/download/yolov8n.pt -RUN curl -Lo /yolov8s-world.pt https://github.com/ultralytics/assets/releases/latest/download/yolov8s-world.pt - -RUN apt-get install -y libeigen3-dev python3-tf2-kdl -RUN apt-get update && apt-get install -y ros-noetic-tf2-geometry-msgs -RUN echo "export MESA_GL_VERSION_OVERRIDE=3.3" >> /root/.bashrc - -# Install additional dependencies for the embedded node -# Install tmux, vim, git, and htop in a single RUN command -RUN apt-get update && apt-get install -y tmux vim git htop socat - -# Copy embedded Arduino code in the Arduino libraries folder -COPY ./devices/Arduino/HydrusModule /root/Arduino/libraries/HydrusModule - -COPY ./ /catkin_ws/src/hydrus-software-stack -WORKDIR /catkin_ws/src/hydrus-software-stack -CMD ["/bin/bash", "-c", "sleep infinity"] diff --git a/docker/amd64/foxy.Dockerfile b/docker/amd64/foxy.Dockerfile new file mode 100644 index 0000000..04f8a9b --- /dev/null +++ b/docker/amd64/foxy.Dockerfile @@ -0,0 +1,50 @@ +# Hydrus ROS 2 (Foxy) layer built on shared base +FROM ubuntu:20.04 + +ENV DEBIAN_FRONTEND=noninteractive \ + LANG=en_US.UTF-8 \ + LC_ALL=en_US.UTF-8 + +# Common packages for all Hydrus images +RUN apt-get update && apt-get install -y --no-install-recommends \ + build-essential \ + gcc \ + cmake \ + git \ + wget \ + curl \ + lsb-release \ + gnupg \ + software-properties-common \ + ca-certificates \ + tmux \ + vim \ + htop \ + socat \ + locales \ + && locale-gen en_US en_US.UTF-8 \ + && update-locale LC_ALL=en_US.UTF-8 LANG=en_US.UTF-8 \ + && rm -rf /var/lib/apt/lists/* + + +ENV ROS_DISTRO=foxy \ + LANG=en_US.UTF-8 \ + LC_ALL=en_US.UTF-8 + +# Configure locale and add ROS 2 apt repository; install core + dev tools +RUN locale-gen en_US en_US.UTF-8 && update-locale LC_ALL=en_US.UTF-8 LANG=en_US.UTF-8 \ + && echo "deb [arch=$(dpkg --print-architecture)] http://packages.ros.org/ros2/ubuntu $(lsb_release -sc) main" > /etc/apt/sources.list.d/ros2.list \ + && curl -sSL https://raw.githubusercontent.com/ros/rosdistro/master/ros.key | apt-key add - \ + && apt-get update \ + && apt-get install -y --no-install-recommends \ + ros-foxy-ros-base \ + ros-foxy-tf2-ros \ + ros-foxy-tf2-geometry-msgs \ + ros-foxy-image-transport \ + ros-foxy-rviz2 \ + ros-foxy-rqt \ + ros-foxy-rosbag2 \ + ros-foxy-ros2bag \ + ros-foxy-rqt-image-view \ + python3-colcon-common-extensions \ + && rm -rf /var/lib/apt/lists/* diff --git a/docker/amd64/noetic.Dockerfile b/docker/amd64/noetic.Dockerfile new file mode 100644 index 0000000..fca2418 --- /dev/null +++ b/docker/amd64/noetic.Dockerfile @@ -0,0 +1,46 @@ +# Hydrus ROS 1 (Noetic) layer built on shared base +FROM ubuntu:20.04 + +ENV DEBIAN_FRONTEND=noninteractive \ + LANG=en_US.UTF-8 \ + LC_ALL=en_US.UTF-8 + +# Common packages for all Hydrus images +RUN apt-get update && apt-get install -y --no-install-recommends \ + build-essential \ + gcc \ + cmake \ + git \ + wget \ + curl \ + lsb-release \ + gnupg \ + software-properties-common \ + ca-certificates \ + tmux \ + vim \ + htop \ + socat \ + locales \ + && locale-gen en_US en_US.UTF-8 \ + && update-locale LC_ALL=en_US.UTF-8 LANG=en_US.UTF-8 \ + && rm -rf /var/lib/apt/lists/* + + +# Add ROS 1 repo and install core + commonly used packages +RUN echo "deb http://packages.ros.org/ros/ubuntu $(lsb_release -sc) main" > /etc/apt/sources.list.d/ros-latest.list \ + && curl -sSL https://raw.githubusercontent.com/ros/rosdistro/master/ros.asc | apt-key add - \ + && apt-get update \ + && apt-get install -y --no-install-recommends \ + ros-noetic-ros-base \ + ros-noetic-tf2-geometry-msgs \ + python3-tf2-kdl \ + ros-noetic-rviz \ + ros-noetic-rqt \ + ros-noetic-rosbag \ + ros-noetic-image-view \ + ros-noetic-tf \ + ros-noetic-tf2-ros \ + ros-noetic-image-transport \ + ros-noetic-laser-proc \ + && rm -rf /var/lib/apt/lists/* diff --git a/docker/amd64/pkgmanagers.Dockerfile b/docker/amd64/pkgmanagers.Dockerfile new file mode 100644 index 0000000..e724c09 --- /dev/null +++ b/docker/amd64/pkgmanagers.Dockerfile @@ -0,0 +1,39 @@ +# Hydrus package managers layer: Python uv, Arduino CLI, Cargo +# Inherit from either the base or a ROS layer depending on use case +ARG PARENT_IMAGE +FROM ${PARENT_IMAGE} + +# Ensure common env is present if base is used +ENV PATH="/root/.local/bin:${PATH}" + +# Python and venv utilities +RUN apt-get update && apt-get install -y --no-install-recommends \ + python3 \ + python3-venv \ + python3-pip \ + && rm -rf /var/lib/apt/lists/* + +# Install uv (https://astral.sh/uv) +RUN curl -LsSf https://astral.sh/uv/install.sh | sh + +# Install Arduino CLI (package manager for Arduino cores/libs) +RUN curl -fsSL https://raw.githubusercontent.com/arduino/arduino-cli/master/install.sh | sh + +# Install Cargo (Rust package manager) without baking toolchains/deps +RUN apt-get update && apt-get install -y --no-install-recommends cargo && rm -rf /var/lib/apt/lists/* + +# Set cache locations to bind-mounted repo by default; can be overridden at runtime +ENV WORKDIR_PATH=/home/catkin_ws/src/hydrus-software-stack \ + UV_CACHE_DIR=/home/catkin_ws/src/hydrus-software-stack/.uv-cache \ + PIP_CACHE_DIR=/home/catkin_ws/src/hydrus-software-stack/.pip-cache \ + ARDUINO_DATA_DIR=/home/catkin_ws/src/hydrus-software-stack/.arduino15 \ + ARDUINO_SKETCHBOOK_DIR=/home/catkin_ws/src/hydrus-software-stack/Arduino \ + CARGO_HOME=/home/catkin_ws/src/hydrus-software-stack/.cargo \ + RUSTUP_HOME=/home/catkin_ws/src/hydrus-software-stack/.rustup \ + PATH="/root/.local/bin:${CARGO_HOME}/bin:${PATH}" + +# Prepare directories and symlinks for caches +RUN mkdir -p "$ARDUINO_DATA_DIR" "$ARDUINO_SKETCHBOOK_DIR/libraries" "$CARGO_HOME/bin" "$RUSTUP_HOME" \ + && rm -rf /root/.arduino15 /root/Arduino \ + && ln -s "$ARDUINO_DATA_DIR" /root/.arduino15 \ + && ln -s "$ARDUINO_SKETCHBOOK_DIR" /root/Arduino diff --git a/docker/docker-compose-amd64-cpu.yaml b/docker/docker-compose-amd64-cpu.yaml index 46150f7..32927e5 100644 --- a/docker/docker-compose-amd64-cpu.yaml +++ b/docker/docker-compose-amd64-cpu.yaml @@ -1,5 +1,3 @@ -version: '3.8' - services: ros-master: image: ros:noetic-ros-core @@ -14,36 +12,21 @@ services: hydrus_cpu: build: context: ../../hydrus-software-stack - dockerfile: docker/amd64/cpu/hydrus.Dockerfile + args: + PARENT_IMAGE: "noetic:latest" + dockerfile: docker/amd64/pkgmanagers.Dockerfile privileged: true stdin_open: true tty: true ports: - "8000:8000" - - "5000:5000" # Added port for Flask detection viewer - devices: - - "/dev/ttyACM0:/dev/ttyACM0" - - "/dev/ttyACM1:/dev/ttyACM1" # Added ttyACM1 mapping - - "/dev/ttyUSB0:/dev/ttyUSB0" # Added ttyUSB0 mapping - - "/dev/dri:/dev/dri" + - "5000:5000" environment: - ROS_MASTER_URI=http://ros-master:11311 - - ARDUINO_BOARD=arduino:avr:uno - - VOLUME - - ROSBAG_PLAYBACK - - DEBUG_ARDUINO - - DISPLAY=${DISPLAY} # Pass the DISPLAY variable from the host - - RVIZ - - TEST - - NO_BUILD - - TMUX_SESSIONS - - ARDUINO_COMPILE - - VIRTUAL_ARDUINO + - DISPLAY=${DISPLAY} volumes: - "../:/home/catkin_ws/src/hydrus-software-stack" - "/tmp/.X11-unix:/tmp/.X11-unix" - - "../rosbags:/rosbags" - - "../yolo_models:/yolo_models" # Volume for YOLO models command: /bin/bash -c "sleep infinity" depends_on: - ros-master diff --git a/docker/docker-compose-jetson-tx2.yaml b/docker/docker-compose-jetson-tx2.yaml deleted file mode 100644 index 17f4367..0000000 --- a/docker/docker-compose-jetson-tx2.yaml +++ /dev/null @@ -1,66 +0,0 @@ -version: '3.8' - -services: - ros-master: - image: ros:noetic-ros-core - command: stdbuf -o L roscore - ports: - - "11311:11311" - - zed-camera: - build: - context: ../ - dockerfile: jetson/camera.Dockerfile - privileged: true - deploy: - resources: - reservations: - devices: - - driver: nvidia - count: all - capabilities: [gpu] - environment: - - ROS_MASTER_URI=http://ros-master:11311 - depends_on: - - ros-master - - hydrus_jetson: - build: - context: ../ - dockerfile: jetson/hydrus.Dockerfile - privileged: true - deploy: - resources: - reservations: - devices: - - driver: nvidia - count: all - capabilities: [gpu] - stdin_open: true - tty: true - volumes: - - "../:/home/catkin_ws/src" - - "/tmp/.X11-unix:/tmp/.X11-unix" - - "../rosbags:/rosbags" - - "../yolo_models:/yolo_models" # Volume for YOLO models - ports: - - "8000:8000" - devices: - - "/dev/ttyACM0:/dev/ttyACM0" - environment: - - ROS_MASTER_URI=http://ros-master:11311 - - ARDUINO_BOARD=arduino:avr:uno - - VOLUME - - ROSBAG_PLAYBACK - - DEBUG_ARDUINO - - DISPLAY=${DISPLAY} - - RVIZ - - TEST - - NO_BUILD - - TMUX_SESSIONS - - ARDUINO_COMPILE - - VIRTUAL_ARDUINO - command: /bin/bash -c "sleep infinity" - depends_on: - - ros-master - - zed-camera diff --git a/docker/run_docker.py b/docker/run_docker.py deleted file mode 100755 index 9ed4a77..0000000 --- a/docker/run_docker.py +++ /dev/null @@ -1,36 +0,0 @@ -#!/usr/bin/env python3 -""" -Hydrus Docker Deployment - Main Entry Point -Simple wrapper that calls the modular hocker script -""" - -import os -import sys -from pathlib import Path - - -def main(): - """Entry point that delegates to the hocker script""" - hocker_script = Path(__file__).parent / "hydrus-docker" / "hocker" - - if not hocker_script.exists(): - print("โŒ Error: hocker script not found in hydrus-docker folder") - sys.exit(1) - - # Pass all arguments to the hocker script and replace current process - try: - # Use execve to replace current process with hocker - env = os.environ.copy() - os.execve( - sys.executable, [sys.executable, str(hocker_script)] + sys.argv[1:], env - ) - except KeyboardInterrupt: - print("\n๐Ÿ›‘ Operation cancelled by user") - sys.exit(1) - except Exception as e: - print(f"โŒ Error running hocker: {e}") - sys.exit(1) - - -if __name__ == "__main__": - main() diff --git a/docs/ARDUINO_SERIAL_COMMUNICATION.md b/docs/ARDUINO_SERIAL_COMMUNICATION.md deleted file mode 100644 index 31188b1..0000000 --- a/docs/ARDUINO_SERIAL_COMMUNICATION.md +++ /dev/null @@ -1,445 +0,0 @@ -# Arduino Serial Communication System Documentation - -## Overview - -The Hydrus submarine project uses a sophisticated serial communication system to interface between ROS (Robot Operating System) and an Arduino microcontroller. This system handles real-time control of thrusters, depth motors, and torpedo mechanisms while providing comprehensive monitoring and logging capabilities. - -## System Architecture - -``` -โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” -โ”‚ ROS Topics โ”‚โ”€โ”€โ”€โ–ถโ”‚ Serial ROS โ”‚โ”€โ”€โ”€โ–ถโ”‚ Arduino โ”‚ -โ”‚ โ”‚ โ”‚ Bridge โ”‚ โ”‚ Microcontrollerโ”‚ -โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ - โ”‚ - โ–ผ -โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” -โ”‚ Virtual Arduino โ”‚ โ”‚ Serial Monitor โ”‚ โ”‚ Arduino Log โ”‚ -โ”‚ (Fallback) โ”‚ โ”‚ Setup โ”‚ โ”‚ Monitor โ”‚ -โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ -``` - -## Core Components - -### 1. Serial ROS Bridge (`serial_ros_bridge.py`) - -**Purpose**: Acts as the main communication hub between ROS and Arduino. - -**Function**: -- Subscribes to ROS topics for thruster control -- Converts ROS messages to Arduino serial commands -- Maintains bidirectional communication with Arduino -- Handles connection recovery and error management - -**ROS Topics Subscribed**: -- `/hydrus/thrusters/1-4` (Int16): Individual thruster PWM values (1000-2000) -- `/hydrus/depth` (Int16): Depth motor PWM control -- `/hydrus/torpedo` (Int16): Torpedo mechanism control - -**Serial Command Format**: -``` -T1:1500 # Thruster 1 with PWM value 1500 (neutral) -T2:1650 # Thruster 2 with PWM value 1650 (forward) -T3:1350 # Thruster 3 with PWM value 1350 (reverse) -T4:1500 # Thruster 4 with PWM value 1500 (neutral) -D:1600 # Depth motors with PWM value 1600 -P:1500 # Torpedo with PWM value 1500 (neutral) -C:30 # Camera motor angle (30 degrees) -``` - -**Key Features**: -- **PWM Range**: 1000-2000 (standard ESC range) -- **Neutral Position**: 1500 (no movement) -- **Thread-based Response Reading**: Continuous monitoring of Arduino responses -- **Connection Management**: Automatic reconnection attempts -- **Command Formatting**: Proper termination and encoding - -### 2. Virtual Arduino System (`virtual_arduino.py`) - -**Purpose**: Provides a fallback when no physical Arduino is connected. - -**How it Works**: - -#### SOCAT Usage (Virtual Arduino Only) -The virtual Arduino system uses `socat` (Socket Cat) to create virtual serial ports **only when no physical Arduino is connected**: - -```bash -socat pty,link=/dev/ttyACM0,raw,echo=0 pty,raw,echo=0 -``` - -**SOCAT Parameters Explained**: -- `pty`: Creates a pseudo-terminal -- `link=/dev/ttyACM0`: Creates a symbolic link to the specified device path -- `raw`: Sets raw mode (no character processing) -- `echo=0`: Disables echo - -**Important**: SOCAT is NOT used for normal serial monitoring - it's only used in `virtual_arduino.py` to create fake devices when testing without hardware. - -#### Virtual Device Creation Process -1. **Check Existing Device**: Verifies if real Arduino exists at `/dev/ttyACM0` -2. **Install Dependencies**: Automatically installs `socat` if missing -3. **Create Virtual Port**: Uses socat to create bidirectional virtual serial communication -4. **Set Permissions**: Ensures proper read/write permissions (0o666) -5. **Response Simulation**: Mimics Arduino responses to commands - -#### Simulated Responses -```python -# Command: "T1:1500" -# Response: "ACK:T1:1500" - -# Command: "STATUS" -# Response: "STATUS:OK,THRUSTERS:8,DEPTH:0.0" - -# Command: "PING" -# Response: "PONG" -``` - -### 3. Serial Monitor Setup (`setup_serial_monitor.py`) - -**Purpose**: Establishes direct serial port monitoring and logging infrastructure. - -**Process Flow**: - -1. **Port Configuration**: - ```bash - stty -F /dev/ttyACM0 raw speed 115200 cs8 -cstopb -parenb - ``` - - `raw`: Raw mode (no line processing) - - `speed 115200`: Baud rate setting - - `cs8`: 8 data bits - - `-cstopb`: 1 stop bit - - `-parenb`: No parity - -2. **Directory Setup**: - - Creates `/tmp/hydrus_serial/` directory - - Initializes log files and PID tracking - -3. **Direct Serial Logging**: - ```python - # Directly reads from Arduino device (no SOCAT involved) - cat_process = subprocess.Popen(["cat", "/dev/ttyACM0"], stdout=log_file) - ``` - **Note**: This setup does NOT use SOCAT - it reads directly from the physical device. - -4. **PID Management**: - - Saves process ID to `/tmp/hydrus_serial/catpid.txt` - - Enables process monitoring and cleanup - -### 4. Arduino Log Monitor (`monitor_arduino_logs.py`) - -**Purpose**: Provides real-time monitoring of Arduino serial communication. - -**Functionality**: - -#### Log File Monitoring -- **Target File**: `/tmp/hydrus_serial/arduinolog.txt` -- **Monitoring Method**: Python-based `tail -f` equivalent -- **Real-time Display**: Continuous output of Arduino responses - -#### Monitoring Process -```python -# Wait for log file creation -while not log_file.exists(): - time.sleep(1) - -# Continuous monitoring -with open(log_file, "r") as f: - f.seek(0, 2) # Go to end of file - while True: - line = f.readline() - if line: - print(line.rstrip()) - else: - time.sleep(0.1) -``` - -## Data Flow Architecture - -### Command Flow (ROS โ†’ Arduino) -``` -1. ROS Node publishes to /hydrus/thrusters/1 - โ†“ -2. serial_ros_bridge.py receives Int16 message - โ†“ -3. Convert to command: "T1:1500" - โ†“ -4. Send via serial.write() to /dev/ttyACM0 - โ†“ -5. Arduino receives and processes command - โ†“ -6. Arduino sends acknowledgment: "ACK:T1:1500" -``` - -### Logging Flow (Arduino โ†’ Monitoring) -``` -1. Arduino sends response via serial - โ†“ -2. setup_serial_monitor.py cat process reads directly from /dev/ttyACM0 - โ†“ -3. Data written to /tmp/hydrus_serial/arduinolog.txt - โ†“ -4. monitor_arduino_logs.py reads and displays log - โ†“ -5. Real-time display in tmux pane -``` - -**Note**: No SOCAT involved in logging - direct device access only. - -## File System Structure - -``` -/tmp/hydrus_serial/ -โ”œโ”€โ”€ arduinolog.txt # Main Arduino communication log -โ”œโ”€โ”€ catpid.txt # Process ID of cat monitoring process -โ””โ”€โ”€ (other temp files) - -/dev/ -โ”œโ”€โ”€ ttyACM0 # Primary Arduino device (real or virtual) -โ””โ”€โ”€ (other devices) -``` - -## Integration with TMUX Sessions - -The system integrates with the main Hydrus tmux session manager: - -### Arduino Window Layout -``` -โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” -โ”‚ Setup Serial Monitor โ”‚ Arduino Logs โ”‚ -โ”‚ (setup_serial_monitor) โ”‚ (monitor_logs) โ”‚ -โ”‚ โ”‚ โ”‚ -โ”‚ - Port configuration โ”‚ - Real-time โ”‚ -โ”‚ - Directory setup โ”‚ log display โ”‚ -โ”‚ - Background logging โ”‚ - Error โ”‚ -โ”‚ โ”‚ monitoring โ”‚ -โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ -``` - -### Session Commands -```bash -# Arduino window pane 0: Setup -bash ${CATKIN_WS}/src/hydrus-software-stack/scripts/setup_serial_monitor.py - -# Arduino window pane 1: Monitor -python3 ${CATKIN_WS}/src/hydrus-software-stack/scripts/monitor_arduino_logs.py -``` - -## Error Handling and Recovery - -### Connection Recovery -- **Automatic Reconnection**: serial_ros_bridge.py attempts reconnection on failure -- **Graceful Degradation**: System continues operation with virtual Arduino -- **Status Monitoring**: Continuous connection health checks - -### Error Types and Responses -1. **Physical Disconnection**: Switches to virtual Arduino -2. **Permission Issues**: Automatic permission setting (chmod 666) -3. **Serial Timeout**: Connection reset and retry -4. **Process Crashes**: PID-based process monitoring and restart - -### Cleanup Procedures -```python -def _cleanup(self): - # Stop cat process - if self.cat_process: - self.cat_process.terminate() - - # Kill socat process - if hasattr(self, "socat_process"): - os.killpg(os.getpgid(self.socat_process.pid), signal.SIGTERM) - - # Remove virtual device - if os.path.exists(self.device_path): - os.unlink(self.device_path) -``` - -## Security and Permissions - -### Device Access -- **Permission Setting**: Automatic chmod 666 on device creation -- **User Groups**: Proper dialout group membership required -- **Directory Permissions**: /tmp/hydrus_serial/ with appropriate access - -### Process Management -- **Signal Handling**: Proper SIGINT/SIGTERM handling -- **Process Groups**: SOCAT runs in separate process group -- **PID Tracking**: All processes tracked for cleanup - -## Performance Considerations - -### Serial Communication -- **Baud Rate**: 115200 bps (optimal for submarine control) -- **Command Timing**: 10ms delay between commands to prevent merging -- **Buffer Management**: Proper serial buffer flushing - -### System Resources -- **Thread Usage**: Dedicated thread for Arduino response reading -- **Memory**: Minimal memory footprint with efficient logging -- **CPU**: Low CPU usage with optimized polling intervals - -## Troubleshooting Guide - -### Common Issues -1. **Device Not Found**: Check USB connection, try virtual Arduino -2. **Permission Denied**: Verify user in dialout group -3. **No Response**: Check baud rate, verify Arduino firmware -4. **Log File Missing**: Ensure setup_serial_monitor.py ran successfully - -### Diagnostic Commands -```bash -# Check device existence -ls -la /dev/ttyACM* - -# Test serial communication -echo "PING" > /dev/ttyACM0 - -# Monitor process status -cat /tmp/hydrus_serial/catpid.txt - -# Check log contents -tail -f /tmp/hydrus_serial/arduinolog.txt -``` - -## Future Enhancements - -### Planned Improvements -1. **Protocol Versioning**: Structured command/response protocol -2. **Error Reporting**: Enhanced error codes from Arduino -3. **Configuration Management**: Dynamic parameter adjustment -4. **Performance Metrics**: Communication latency monitoring -5. **Security**: Encrypted communication for sensitive operations - -This comprehensive serial communication system ensures reliable, monitored, and fault-tolerant communication between the ROS-based submarine control system and the Arduino microcontroller, with complete logging and monitoring capabilities for debugging and operational oversight. - -## Architecture Efficiency Analysis - -### Current Implementation Limitations - -The current architecture has several efficiency issues: - -#### File I/O Bottleneck -``` -Arduino โ†’ /dev/ttyACM0 โ†’ cat process โ†’ file write โ†’ monitor process โ†’ display - โ†“ - serial_ros_bridge (direct access) -``` - -**Problems with Current Approach**: - -1. **Constant File Writing**: `cat /dev/ttyACM0 > arduinolog.txt` continuously writes to disk -2. **File Reading Overhead**: `monitor_arduino_logs.py` must constantly poll and read the file -3. **Disk I/O Latency**: File system operations introduce latency in log display -4. **Resource Waste**: Unnecessary disk writes for ephemeral monitoring data -5. **Limited Scalability**: Adding more monitoring processes requires more file I/O - -#### Performance Metrics -- **File I/O**: ~10-100ms latency per write/read cycle -- **Disk Usage**: Continuous writing (potentially GB/day depending on Arduino output) -- **CPU Overhead**: File system calls, polling, buffer management -- **Memory**: File buffers in kernel and userspace - -### Proposed SOCAT-Based Architecture - -A more efficient approach would use SOCAT to create virtual serial port multiplexing: - -``` - โ”Œโ”€ /dev/ttyVIRT0 โ†’ serial_ros_bridge.py -Arduino โ†’ /dev/ttyACM0 โ†’ SOCAT โ”ค - โ””โ”€ /dev/ttyVIRT1 โ†’ monitor_display.py -``` - -#### SOCAT Multiplexing Command -```bash -# Create bidirectional virtual ports -socat -d -d pty,raw,echo=0,link=/dev/ttyVIRT0 pty,raw,echo=0,link=/dev/ttyVIRT1 & - -# Bridge physical device to virtual ports -socat /dev/ttyACM0,raw,echo=0 EXEC:'tee >(socat - /dev/ttyVIRT0) | socat - /dev/ttyVIRT1' -``` - -#### Benefits of SOCAT Approach - -1. **Zero File I/O**: Direct memory-to-memory data streaming -2. **Real-time Access**: Multiple processes can access serial data simultaneously -3. **Lower Latency**: ~1-10ms vs 10-100ms for file-based approach -4. **Better Resource Usage**: No disk writes for monitoring -5. **Scalability**: Easy to add more virtual endpoints -6. **Isolation**: Each consumer gets independent data stream - -#### Advanced SOCAT Architecture -```bash -# Master serial multiplexer -socat /dev/ttyACM0,raw,echo=0,b115200 \ - EXEC:'tee >(socat - pty,link=/dev/hydrus_control,raw,echo=0) \ - >(socat - pty,link=/dev/hydrus_monitor,raw,echo=0) \ - >(socat - pty,link=/dev/hydrus_log,raw,echo=0)' -``` - -**Result**: -- `/dev/hydrus_control` โ†’ serial_ros_bridge.py (bidirectional) -- `/dev/hydrus_monitor` โ†’ real-time monitoring display -- `/dev/hydrus_log` โ†’ optional file logging (if needed) - -### Performance Comparison - -| Aspect | Current (File-based) | Proposed (SOCAT-based) | -|--------|---------------------|------------------------| -| Latency | 10-100ms | 1-10ms | -| Disk I/O | Continuous | Optional/None | -| CPU Usage | High (file ops) | Low (memory ops) | -| Scalability | Poor | Excellent | -| Real-time | No | Yes | -| Resource Usage | High | Low | - -### Implementation Considerations - -#### Advantages of SOCAT Approach -- **Performance**: Significantly reduced latency and resource usage -- **Flexibility**: Easy to add/remove monitoring endpoints -- **Real-time**: True real-time data access for all consumers -- **Reliability**: Less failure points (no file system dependencies) - -#### Potential Drawbacks -- **Complexity**: More complex setup and process management -- **Dependencies**: Requires SOCAT to be properly installed and configured -- **Debugging**: Harder to debug virtual port issues -- **Process Management**: More processes to monitor and restart - -### Recommended Architecture Migration - -#### Phase 1: Hybrid Approach -- Keep current file-based logging as backup -- Add SOCAT virtual ports for real-time monitoring -- Test performance improvements - -#### Phase 2: Full Migration -- Replace file-based monitoring with SOCAT endpoints -- Implement proper process management for SOCAT multiplexer -- Add configuration management for virtual port creation - -#### Implementation Script Example -```python -def setup_socat_multiplexer(device="/dev/ttyACM0"): - """Setup SOCAT-based serial multiplexing""" - - # Create virtual device paths - control_port = "/dev/hydrus_control" - monitor_port = "/dev/hydrus_monitor" - log_port = "/dev/hydrus_log" - - # SOCAT multiplexer command - socat_cmd = [ - "socat", - f"{device},raw,echo=0,b115200", - f"EXEC:tee >(socat - pty,link={control_port},raw,echo=0) " + - f">(socat - pty,link={monitor_port},raw,echo=0) " + - f">(socat - pty,link={log_port},raw,echo=0)" - ] - - # Start multiplexer process - process = subprocess.Popen(socat_cmd, preexec_fn=os.setsid) - - return process, [control_port, monitor_port, log_port] -``` - -**Conclusion**: You are absolutely correct - the current file-based approach is inefficient. A SOCAT-based virtual port multiplexing system would provide significant performance improvements, better resource utilization, and true real-time monitoring capabilities. diff --git a/docs/PROFILING.md b/docs/PROFILING.md deleted file mode 100644 index a369af6..0000000 --- a/docs/PROFILING.md +++ /dev/null @@ -1,300 +0,0 @@ -# Function and ROS Node Profiling Guide - -This document explains how to instrument and profile your Hydrus software using the provided profiling decorators and ROS node profiler. - -## Table of Contents - -- [Overview](#overview) -- [Prerequisites](#prerequisites) -- [Profiling Decorators](#profiling-decorators) - - [Installation](#installation) - - [Decorator Usage](#decorator-usage) - - [Context Manager Usage](#context-manager-usage) - - [Example: `profiling_decorators.py`](#example-profiling_decoratorspy) -- [Instrumenting Your Code](#instrumenting-your-code) - - [Python Functions](#python-functions) - - [Class Methods](#class-methods) - - [ROS Callbacks](#ros-callbacks) -- [Example Profiling Script](#example-profiling-script) -- [ROS Node Profiler](#ros-node-profiler) - - [Usage](#usage) - - [CLI Options](#cli-options) -- [Exporting and Analyzing Results](#exporting-and-analyzing-results) -- [Best Practices](#best-practices) - ---- - -## Overview - -Hydrus provides two complementary profiling tools: - -1. **Function-level Decorators**: Lightweight decorators and context managers for measuring execution time of individual functions or code blocks. -2. **ROS Node Profiler**: A standalone ROS node (`ros_profiler.py`) for real-time resource monitoring and FPS measurement of active ROS nodes. - ---- - -## Prerequisites - -- Python 3.8+ environment -- ROS (Noetic/Melodic) installed and sourced in your environment -- `psutil` and `colorama` Python packages installed for the ROS profiler: - -```bash -pip install psutil colorama -``` - -**Important**: Make sure to source your ROS environment before running any profiling tools: - -```bash -# Source ROS -source /opt/ros/noetic/setup.bash # or melodic - -# Source your workspace (if built) -source devel/setup.bash -``` - ---- - -## Profiling Decorators - -### Installation - -The decorators and utilities live in `autonomy/scripts/profiler/profiling_decorators.py`. No additional install steps are needed if your ROS package path includes this folder. - -### Decorator Usage - -- `@profile_function(name?: str, category: str="general")` -- `@profile_method(name?: str, category: str="methods")` -- `@profile_ros_callback(topic_name: str)` - -Apply these directly above the function or method definition: - -```python -from autonomy.scripts.profiler.profiling_decorators import profile_function, profile_method, profile_ros_callback - -@profile_function("cv.yolo_inference", "cv") -def yolo_object_detection(image): - # ... - pass - -@profile_method(category="mission") -def run(self): - # ... - pass - -@profile_ros_callback("camera/image_raw") -def image_callback(msg): - # ... - pass -``` - -### Context Manager Usage - -Use `FunctionProfiler(name: str)` to wrap arbitrary code blocks: - -```python -from autonomy.scripts.profiler.profiling_decorators import FunctionProfiler - -with FunctionProfiler("cv.contour_detection"): - contours, _ = cv2.findContours(mask, ...) -``` - -### Example: `profiling_decorators.py` - -See `autonomy/scripts/profiler/profiling_decorators.py` for full implementation details and helper functions: - -- `ProfileManager` singleton collects statistics -- `export_profiling_data(filename)` to dump JSON - ---- - -## Instrumenting Your Code - -### Python Functions - -Decorate any standalone function: - -```python -@profile_function("depth.calculation") -def calculate_depth(points, intrinsics): - # core logic - return points_3d -``` - -### Class Methods - -Decorate methods inside your classes: - -```python -class MissionPlanner: - - @profile_method(category="mission") - def feedback_callback(self, feedback): - # process feedback - pass -``` - -### ROS Callbacks - -Wrap ROS subscriber callbacks: - -```python -@profile_ros_callback("/detector/box_detection") -def detection_callback(msg): - # parse detections - pass -``` - ---- - -## Example Profiling Script - -An example instrumentation is provided in `autonomy/scripts/profiler/profiling_example.py`. To run: - -```bash -# First, ensure ROS is sourced -source /opt/ros/noetic/setup.bash # or melodic -source devel/setup.bash # if workspace is built - -# Method 1: Direct execution -python3 autonomy/scripts/profiler/profiling_example.py - -# Method 2: Using rosrun (after making executable) -chmod +x autonomy/scripts/profiler/profiling_example.py -rosrun autonomy profiling_example.py -``` - -**Note**: The example script requires ROS to be properly sourced and `rospy` to be available. - -### Simple Profiling Test (No ROS Required) - -A standalone test without ROS dependencies is available in `autonomy/scripts/profiler/profiling_test.py`: - -```bash -# Run the simple profiling test -python3 autonomy/scripts/profiler/profiling_test.py - -# This will: -# - Test all profiling decorators -# - Generate mock data and run profiled functions -# - Display performance results -# - Export detailed JSON results -``` - -This test is useful for: -- Verifying profiling functionality without ROS -- Understanding profiling output format -- Testing on systems without ROS installed - -Inside the example script, you will see usage of: -- `ProfileManager.get_instance().set_node_name("cv_publisher")` -- Decorators and `FunctionProfiler` blocks -- Automatic export via `atexit` to `cv_publisher_profile.json` - ---- - -## ROS Node Profiler - -The ROS Node Profiler (`autonomy/scripts/profiler/ros_profiler.py`) monitors: -- CPU & memory usage per node -- Topic publish rates (FPS) -- Optional function-level profiling if your nodes use the decorators - -### Usage - -1. Compile and source your workspace: - - ```bash - # Build the workspace - catkin_make - source devel/setup.bash - ``` - -2. Run the profiler with: - - ```bash - # Method 1: Direct execution - python3 autonomy/scripts/profiler/ros_profiler.py [options] - - # Method 2: Using rosrun (after making executable) - chmod +x autonomy/scripts/profiler/ros_profiler.py - rosrun autonomy ros_profiler.py [options] - ``` - -3. View real-time console dashboard. - -**Example commands:** - -```bash -# Monitor all nodes with default settings -python3 autonomy/scripts/profiler/ros_profiler.py - -# Monitor specific nodes only -python3 autonomy/scripts/profiler/ros_profiler.py --nodes cv_publisher controller_action - -# Enable function-level profiling and export data -python3 autonomy/scripts/profiler/ros_profiler.py --profile-functions --export profile_data.csv - -# Custom update rate -python3 autonomy/scripts/profiler/ros_profiler.py --update-rate 1.0 -``` - -### CLI Options - -- `--nodes`: List of specific node names to monitor (default = all) -- `--profile-functions`: Include function-level stats (requires your nodes to use decorators) -- `--export`: CSV filename to record data over time -- `--update-rate`: Sampling interval in seconds (default = 0.5s) - ---- - -## Exporting and Analyzing Results - -- **Function Decorator Exports**: Use `export_profiling_data("output.json")` at shutdown to dump collected JSON. -- **ROS Node Profiler Exports**: CSV export via `--export` and summary JSON created alongside. - -Imported JSON can be visualized with tools like Excel, Python scripts, or custom dashboards. - ---- - -## Best Practices - -- Profile only critical or slow code paths to reduce overhead. -- Use categories and clear naming conventions for easy filtering. -- Combine real-time `ros_profiler` with offline JSON analysis for comprehensive insight. - -## Troubleshooting - -### Common Issues - -**1. "ModuleNotFoundError: No module named 'rospy'"** -```bash -# Solution: Source your ROS environment -source /opt/ros/noetic/setup.bash # or melodic -source devel/setup.bash # if workspace is built -``` - -**2. "rosrun: Couldn't find executable"** -```bash -# Solution: Make script executable and ensure it's in the right location -chmod +x autonomy/scripts/profiler/profiling_example.py -# Or run directly with python3 -python3 autonomy/scripts/profiler/profiling_example.py -``` - -**3. "package 'autonomy' not found"** -```bash -# Solution: Build and source your workspace -catkin_make -source devel/setup.bash -``` - -**4. Missing dependencies for ros_profiler.py** -```bash -# Solution: Install required Python packages -pip install psutil colorama -``` - ---- - -_This guide helps you leverage detailed profiling across your Hydrus software stack for performance tuning and diagnostics._ diff --git a/docs/SOCAT_ARDUINO_MULTIPLEXER_REFACTORING.md b/docs/SOCAT_ARDUINO_MULTIPLEXER_REFACTORING.md deleted file mode 100644 index 26c1c7a..0000000 --- a/docs/SOCAT_ARDUINO_MULTIPLEXER_REFACTORING.md +++ /dev/null @@ -1,771 +0,0 @@ -# SOCAT Virtual Serial Port Multiplexing - Refactoring Guide - -## Overview - -This document provides a comprehensive guide to refactor the Hydrus Arduino serial communication system to use SOCAT virtual device ports, enabling multiple processes to access the same Arduino while maintaining proper write access control. - -## Current Architecture Problems - -### Existing Issues -``` -Arduino (/dev/ttyACM0) โ†’ Multiple competing processes -``` - -**Problems:** -- Only one process can open `/dev/ttyACM0` at a time -- File-based logging creates I/O bottlenecks -- No concurrent monitoring during ROS operation -- Manual testing requires stopping all ROS processes - -## Proposed SOCAT Architecture - -### Design Principles -1. **Single Writer**: Only one process can write to Arduino -2. **Multiple Readers**: Multiple processes can read Arduino responses -3. **Virtual Multiplexing**: SOCAT creates virtual endpoints -4. **Process Isolation**: Each consumer gets independent data stream - -### Architecture Diagram -``` - โ”Œโ”€ /dev/hydrus_control (RW) โ†’ serial_ros_bridge.py -Arduino (/dev/ttyACM0) โ†’ SOCAT โ”€โ”€โ”ค - โ”œโ”€ /dev/hydrus_monitor (RO) โ†’ monitor_display.py - โ”œโ”€ /dev/hydrus_debug (RO) โ†’ manual testing - โ””โ”€ /dev/hydrus_log (RO) โ†’ file logging (optional) -``` - -## Implementation Strategy - -### Phase 1: SOCAT Multiplexer Setup - -#### 1.1 Master Multiplexer Script - -Create `scripts/socat_arduino_multiplexer.py`: - -```python -#!/usr/bin/env python3 -""" -SOCAT Arduino Multiplexer -Creates virtual serial ports for multiple Arduino access -""" - -import os -import signal -import subprocess -import sys -import time -from pathlib import Path -from typing import Dict, List, Optional - - -class ArduinoMultiplexer: - def __init__(self, arduino_device="/dev/ttyACM0", baud_rate=115200): - self.arduino_device = arduino_device - self.baud_rate = baud_rate - - # Virtual device paths - self.control_port = "/dev/hydrus_control" # Read/Write for ROS bridge - self.monitor_port = "/dev/hydrus_monitor" # Read-only for monitoring - self.debug_port = "/dev/hydrus_debug" # Read-only for manual testing - self.log_port = "/dev/hydrus_log" # Read-only for file logging - - # Process management - self.socat_processes: Dict[str, subprocess.Popen] = {} - self.running = False - - # Setup signal handlers - signal.signal(signal.SIGINT, self._signal_handler) - signal.signal(signal.SIGTERM, self._signal_handler) - - def _check_dependencies(self): - """Ensure SOCAT is installed""" - try: - subprocess.run(["socat", "-V"], capture_output=True, check=True) - print("โœ“ SOCAT found") - except (subprocess.CalledProcessError, FileNotFoundError): - print("Installing SOCAT...") - subprocess.run(["apt-get", "update"], check=True) - subprocess.run(["apt-get", "install", "-y", "socat"], check=True) - - def _check_arduino_device(self): - """Verify Arduino device exists and is accessible""" - if not os.path.exists(self.arduino_device): - raise FileNotFoundError(f"Arduino device {self.arduino_device} not found") - - if not os.access(self.arduino_device, os.R_OK | os.W_OK): - raise PermissionError(f"No read/write access to {self.arduino_device}") - - def _create_virtual_port(self, name: str, virtual_path: str, readonly: bool = False) -> subprocess.Popen: - """Create a single virtual serial port""" - - # Remove existing virtual device - if os.path.exists(virtual_path): - os.unlink(virtual_path) - - # Create virtual port with SOCAT - if readonly: - # Read-only virtual port - socat_cmd = [ - "socat", - f"pty,link={virtual_path},raw,echo=0,user=nobody,group=dialout,perm=0644", - f"pipe:/tmp/hydrus_arduino_data" - ] - else: - # Read/write virtual port (control port) - socat_cmd = [ - "socat", - f"pty,link={virtual_path},raw,echo=0,user=nobody,group=dialout,perm=0666", - f"{self.arduino_device},raw,echo=0,b{self.baud_rate}" - ] - - print(f"Creating {name} virtual port: {virtual_path}") - process = subprocess.Popen( - socat_cmd, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - preexec_fn=os.setsid - ) - - return process - - def _create_data_splitter(self) -> subprocess.Popen: - """Create data splitter that feeds multiple read-only ports""" - - # Create named pipe for data distribution - pipe_path = "/tmp/hydrus_arduino_data" - if os.path.exists(pipe_path): - os.unlink(pipe_path) - os.mkfifo(pipe_path, 0o666) - - # SOCAT command to split Arduino data to multiple outputs - tee_targets = f">(socat - pty,link={self.monitor_port},raw,echo=0,perm=0444) " + \ - f">(socat - pty,link={self.debug_port},raw,echo=0,perm=0444) " + \ - f">(socat - pty,link={self.log_port},raw,echo=0,perm=0444)" - - splitter_cmd = [ - "bash", "-c", - f"socat {self.arduino_device},raw,echo=0,b{self.baud_rate} " + - f"EXEC:'tee {tee_targets}'" - ] - - print("Creating data splitter...") - process = subprocess.Popen( - splitter_cmd, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - preexec_fn=os.setsid - ) - - return process - - def start_multiplexer(self): - """Start the complete multiplexer system""" - print("Starting Arduino SOCAT Multiplexer...") - - try: - # Check dependencies and device - self._check_dependencies() - self._check_arduino_device() - - # Method 1: Simple approach with single control port + data splitter - self._start_simple_multiplexer() - - self.running = True - print("โœ“ Arduino multiplexer started successfully") - print(f"โœ“ Control port (RW): {self.control_port}") - print(f"โœ“ Monitor port (RO): {self.monitor_port}") - print(f"โœ“ Debug port (RO): {self.debug_port}") - print(f"โœ“ Log port (RO): {self.log_port}") - - except Exception as e: - print(f"โœ— Failed to start multiplexer: {e}") - self.stop_multiplexer() - raise - - def _start_simple_multiplexer(self): - """Simple multiplexer implementation""" - - # 1. Create control port (bidirectional) - control_process = self._create_virtual_port("Control", self.control_port, readonly=False) - self.socat_processes["control"] = control_process - time.sleep(1) # Allow port creation - - # 2. Create data splitter for read-only ports - splitter_process = self._create_data_splitter() - self.socat_processes["splitter"] = splitter_process - time.sleep(2) # Allow all ports to be created - - # 3. Verify all ports exist - self._verify_ports() - - def _verify_ports(self): - """Verify all virtual ports were created successfully""" - ports = [self.control_port, self.monitor_port, self.debug_port, self.log_port] - - for port in ports: - if not os.path.exists(port): - raise RuntimeError(f"Failed to create virtual port: {port}") - print(f"โœ“ Virtual port created: {port}") - - def stop_multiplexer(self): - """Stop all SOCAT processes and cleanup""" - print("Stopping Arduino multiplexer...") - self.running = False - - # Kill all SOCAT processes - for name, process in self.socat_processes.items(): - try: - print(f"Stopping {name} process...") - os.killpg(os.getpgid(process.pid), signal.SIGTERM) - process.wait(timeout=5) - except Exception as e: - print(f"Error stopping {name}: {e}") - try: - os.killpg(os.getpgid(process.pid), signal.SIGKILL) - except: - pass - - # Cleanup virtual device files - ports = [self.control_port, self.monitor_port, self.debug_port, self.log_port] - for port in ports: - try: - if os.path.exists(port): - os.unlink(port) - print(f"โœ“ Removed {port}") - except Exception as e: - print(f"Error removing {port}: {e}") - - # Cleanup named pipes - try: - pipe_path = "/tmp/hydrus_arduino_data" - if os.path.exists(pipe_path): - os.unlink(pipe_path) - except: - pass - - def _signal_handler(self, signum, frame): - """Handle shutdown signals""" - print(f"\nReceived signal {signum}, shutting down...") - self.stop_multiplexer() - sys.exit(0) - - def run_daemon(self): - """Run multiplexer as daemon""" - self.start_multiplexer() - - try: - while self.running: - # Monitor processes health - for name, process in self.socat_processes.items(): - if process.poll() is not None: - print(f"Warning: {name} process died, restarting...") - # Could implement restart logic here - - time.sleep(5) - except KeyboardInterrupt: - pass - finally: - self.stop_multiplexer() - - -def main(): - if len(sys.argv) > 1: - arduino_device = sys.argv[1] - else: - arduino_device = "/dev/ttyACM0" - - multiplexer = ArduinoMultiplexer(arduino_device) - multiplexer.run_daemon() - - -if __name__ == "__main__": - main() -``` - -### Phase 2: Refactor Existing Components - -#### 2.1 Modified Serial ROS Bridge - -Update `autonomy/scripts/controller/serial_ros_bridge.py`: - -```python -#!/usr/bin/env python3 -""" -Modified Serial ROS Bridge using SOCAT virtual ports -""" - -import threading -import time -import rospy -import serial -from std_msgs.msg import Int16 -from termcolor import colored - - -class SerialROSBridge: - def __init__(self): - rospy.init_node("serial_ros_bridge", anonymous=True) - - # Use virtual control port instead of physical device - self.control_port = rospy.get_param("~control_port", "/dev/hydrus_control") - baud_rate = rospy.get_param("~baud_rate", 115200) - - # Serial connection to virtual control port - self.ser = None - self.is_connected = False - self._connect_to_control_port(baud_rate) - - # Start response reading thread - self.running = True - self.response_thread = threading.Thread(target=self._read_responses) - self.response_thread.daemon = True - self.response_thread.start() - - # ROS Subscribers (unchanged) - rospy.Subscriber("/hydrus/thrusters/1", Int16, lambda msg: self.thruster_callback(1, msg)) - rospy.Subscriber("/hydrus/thrusters/2", Int16, lambda msg: self.thruster_callback(2, msg)) - rospy.Subscriber("/hydrus/thrusters/3", Int16, lambda msg: self.thruster_callback(3, msg)) - rospy.Subscriber("/hydrus/thrusters/4", Int16, lambda msg: self.thruster_callback(4, msg)) - rospy.Subscriber("/hydrus/depth", Int16, self.depth_callback) - rospy.Subscriber("/hydrus/torpedo", Int16, self.torpedo_callback) - - rospy.on_shutdown(self.shutdown) - - def _connect_to_control_port(self, baud_rate): - """Connect to SOCAT virtual control port""" - max_retries = 10 - retry_count = 0 - - while retry_count < max_retries and not rospy.is_shutdown(): - try: - if not os.path.exists(self.control_port): - rospy.logwarn(f"Control port {self.control_port} not found, waiting...") - time.sleep(2) - retry_count += 1 - continue - - self.ser = serial.Serial(port=self.control_port, baudrate=baud_rate, timeout=1) - self.is_connected = True - rospy.loginfo(f"Connected to Arduino via {self.control_port}") - time.sleep(2) # Allow Arduino reset - return True - - except serial.SerialException as e: - rospy.logwarn(f"Failed to connect to {self.control_port}: {e}") - time.sleep(2) - retry_count += 1 - - rospy.logerr(f"Failed to connect to control port after {max_retries} attempts") - return False - - # Rest of the class remains the same... - # (send_command, _read_responses, callbacks, shutdown methods unchanged) -``` - -#### 2.2 New Monitoring System - -Create `scripts/arduino_monitor.py`: - -```python -#!/usr/bin/env python3 -""" -Arduino Monitor using SOCAT virtual ports -Real-time monitoring without interfering with ROS bridge -""" - -import os -import time -import serial -from datetime import datetime -from termcolor import colored - - -class ArduinoMonitor: - def __init__(self, monitor_port="/dev/hydrus_monitor"): - self.monitor_port = monitor_port - self.running = False - - def start_monitoring(self): - """Start real-time Arduino monitoring""" - print(colored("Arduino Monitor Starting...", "cyan")) - print(colored(f"Monitoring port: {self.monitor_port}", "yellow")) - print(colored("Press Ctrl+C to stop", "yellow")) - print("=" * 60) - - while not os.path.exists(self.monitor_port): - print(colored("Waiting for monitor port to be available...", "yellow")) - time.sleep(1) - - try: - with serial.Serial(self.monitor_port, 115200, timeout=1) as ser: - self.running = True - print(colored("โœ“ Connected to Arduino monitor", "green")) - - while self.running: - try: - if ser.in_waiting > 0: - line = ser.readline().decode('utf-8').strip() - if line: - timestamp = datetime.now().strftime("%H:%M:%S.%f")[:-3] - print(f"[{colored(timestamp, 'blue')}] {line}") - except Exception as e: - print(colored(f"Monitor error: {e}", "red")) - break - - time.sleep(0.01) - - except Exception as e: - print(colored(f"Failed to connect to monitor port: {e}", "red")) - - def stop_monitoring(self): - """Stop monitoring""" - self.running = False - - -def main(): - monitor = ArduinoMonitor() - try: - monitor.start_monitoring() - except KeyboardInterrupt: - print(colored("\nStopping Arduino monitor...", "yellow")) - monitor.stop_monitoring() - - -if __name__ == "__main__": - main() -``` - -#### 2.3 Enhanced Debug Terminal - -Create `scripts/arduino_debug_terminal.py`: - -```python -#!/usr/bin/env python3 -""" -Arduino Debug Terminal using SOCAT virtual ports -Manual testing without stopping ROS systems -""" - -import os -import sys -import threading -import time -import serial -from termcolor import colored - - -class ArduinoDebugTerminal: - def __init__(self, debug_port="/dev/hydrus_debug", control_port="/dev/hydrus_control"): - self.debug_port = debug_port # Read-only monitoring - self.control_port = control_port # Write access for commands - self.running = False - - def start_terminal(self): - """Start interactive debug terminal""" - print(colored("Arduino Debug Terminal", "cyan", attrs=["bold"])) - print(colored("Commands will be sent via control port", "yellow")) - print(colored("Responses monitored via debug port", "yellow")) - print(colored("Type 'quit' to exit", "yellow")) - print("=" * 50) - - # Wait for ports to be available - while not (os.path.exists(self.debug_port) and os.path.exists(self.control_port)): - print("Waiting for virtual ports...") - time.sleep(1) - - try: - # Open control port for sending commands - control_ser = serial.Serial(self.control_port, 115200, timeout=1) - - # Open debug port for monitoring responses - debug_ser = serial.Serial(self.debug_port, 115200, timeout=1) - - print(colored("โœ“ Connected to Arduino debug interface", "green")) - - # Start response monitoring thread - self.running = True - monitor_thread = threading.Thread(target=self._monitor_responses, args=(debug_ser,)) - monitor_thread.daemon = True - monitor_thread.start() - - # Main command input loop - self._command_loop(control_ser) - - except Exception as e: - print(colored(f"Failed to connect: {e}", "red")) - finally: - self.running = False - - def _monitor_responses(self, debug_ser): - """Monitor Arduino responses in background thread""" - while self.running: - try: - if debug_ser.in_waiting > 0: - line = debug_ser.readline().decode('utf-8').strip() - if line: - print(colored(f"<< {line}", "green")) - except Exception as e: - if self.running: - print(colored(f"Monitor error: {e}", "red")) - break - time.sleep(0.01) - - def _command_loop(self, control_ser): - """Interactive command input loop""" - while self.running: - try: - # Get user input - cmd = input(colored(">> ", "blue")).strip() - - if cmd.lower() in ['quit', 'exit', 'q']: - break - - if cmd: - # Send command to Arduino - control_ser.write(f"{cmd}\n".encode('utf-8')) - control_ser.flush() - print(colored(f">> {cmd}", "blue")) - - except KeyboardInterrupt: - break - except Exception as e: - print(colored(f"Input error: {e}", "red")) - - def stop_terminal(self): - """Stop debug terminal""" - self.running = False - - -def main(): - terminal = ArduinoDebugTerminal() - try: - terminal.start_terminal() - except KeyboardInterrupt: - pass - finally: - print(colored("\nExiting debug terminal...", "yellow")) - terminal.stop_terminal() - - -if __name__ == "__main__": - main() -``` - -### Phase 3: Integration Scripts - -#### 3.1 Startup Script - -Create `scripts/start_arduino_multiplexer.sh`: - -```bash -#!/bin/bash -""" -Start Arduino SOCAT Multiplexer System -""" - -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -ARDUINO_DEVICE="${1:-/dev/ttyACM0}" - -echo "Starting Arduino SOCAT Multiplexer..." -echo "Arduino device: $ARDUINO_DEVICE" - -# Check if Arduino device exists -if [ ! -e "$ARDUINO_DEVICE" ]; then - echo "Error: Arduino device $ARDUINO_DEVICE not found" - echo "Available devices:" - ls /dev/ttyACM* /dev/ttyUSB* 2>/dev/null || echo "No serial devices found" - exit 1 -fi - -# Start multiplexer in background -python3 "$SCRIPT_DIR/socat_arduino_multiplexer.py" "$ARDUINO_DEVICE" & -MULTIPLEXER_PID=$! - -# Save PID for cleanup -echo $MULTIPLEXER_PID > /tmp/arduino_multiplexer.pid - -echo "Arduino multiplexer started with PID: $MULTIPLEXER_PID" -echo "Virtual ports will be available shortly..." - -# Wait for virtual ports to be created -sleep 3 - -# Verify ports exist -if [ -e "/dev/hydrus_control" ] && [ -e "/dev/hydrus_monitor" ]; then - echo "โœ“ Virtual ports created successfully" - echo " Control port (RW): /dev/hydrus_control" - echo " Monitor port (RO): /dev/hydrus_monitor" - echo " Debug port (RO): /dev/hydrus_debug" - echo " Log port (RO): /dev/hydrus_log" -else - echo "โœ— Failed to create virtual ports" - exit 1 -fi - -echo "Arduino multiplexer ready!" -``` - -#### 3.2 Stop Script - -Create `scripts/stop_arduino_multiplexer.sh`: - -```bash -#!/bin/bash -""" -Stop Arduino SOCAT Multiplexer System -""" - -echo "Stopping Arduino SOCAT Multiplexer..." - -# Kill multiplexer process -if [ -f /tmp/arduino_multiplexer.pid ]; then - PID=$(cat /tmp/arduino_multiplexer.pid) - echo "Stopping multiplexer process (PID: $PID)..." - kill $PID 2>/dev/null - rm -f /tmp/arduino_multiplexer.pid -fi - -# Kill any remaining SOCAT processes -pkill -f "socat.*hydrus" 2>/dev/null - -# Clean up virtual devices -echo "Cleaning up virtual devices..." -rm -f /dev/hydrus_control /dev/hydrus_monitor /dev/hydrus_debug /dev/hydrus_log -rm -f /tmp/hydrus_arduino_data - -echo "Arduino multiplexer stopped" -``` - -### Phase 4: Modified TMUX Integration - -#### 4.1 Updated TMUX Sessions - -Modify `start_tmux_sessions.py` to use the new system: - -```python -def _setup_arduino_multiplexer(self): - """Setup Arduino SOCAT multiplexer before starting sessions""" - print("Starting Arduino SOCAT multiplexer...") - - multiplexer_script = self.catkin_ws / "src/hydrus-software-stack/scripts/start_arduino_multiplexer.sh" - - if multiplexer_script.exists(): - self._run_command([str(multiplexer_script)], check=True) - print("โœ“ Arduino multiplexer started") - else: - print("โš  Arduino multiplexer script not found, using fallback") - self._setup_virtual_arduino() # Fallback to existing system - -def _create_arduino_window(self): - """Create Arduino window using new monitoring system""" - print("Creating Arduino window...") - - # Create new window for Arduino monitoring - self._tmux_command(["new-window", "-t", "hydrus:1", "-n", "Arduino"]) - - # First pane: Real-time Arduino monitor - monitor_script = self.catkin_ws / "src/hydrus-software-stack/scripts/arduino_monitor.py" - - if monitor_script.exists(): - monitor_cmd = f"python3 {monitor_script}" - else: - monitor_cmd = f"python3 {self.catkin_ws}/src/hydrus-software-stack/scripts/monitor_arduino_logs.py" - - self._tmux_command([ - "send-keys", "-t", "hydrus:1.0", - f"echo 'Starting Arduino Monitor'; {monitor_cmd}", "C-m" - ]) - - # Second pane: Debug terminal - self._tmux_command(["split-window", "-h", "-t", "hydrus:1"]) - debug_script = self.catkin_ws / "src/hydrus-software-stack/scripts/arduino_debug_terminal.py" - - if debug_script.exists(): - debug_cmd = f"python3 {debug_script}" - else: - debug_cmd = "echo 'Debug terminal not available'" - - self._tmux_command([ - "send-keys", "-t", "hydrus:1.1", - f"echo 'Starting Debug Terminal'; {debug_cmd}", "C-m" - ]) - - # Set layout - self._tmux_command(["select-layout", "-t", "hydrus:1", "even-horizontal"]) -``` - -## Usage Examples - -### Starting the System - -```bash -# 1. Start Arduino multiplexer -./scripts/start_arduino_multiplexer.sh - -# 2. Start ROS systems (now uses /dev/hydrus_control) -./start_tmux_sessions.sh - -# 3. Use debug terminal (concurrent with ROS) -python3 scripts/arduino_debug_terminal.py -``` - -### Manual Testing (Concurrent with ROS) - -```bash -# Terminal 1: Monitor Arduino responses -python3 scripts/arduino_monitor.py - -# Terminal 2: Send manual commands -python3 scripts/arduino_debug_terminal.py ->> T1:1600 -<< ACK:T1:1600 ->> D:1400 -<< ACK:D:1400 - -# Terminal 3: ROS continues operating normally -rostopic pub /hydrus/thrusters/2 std_msgs/Int16 "data: 1550" -``` - -## Benefits of New Architecture - -### Performance Improvements -- **Zero file I/O** for monitoring -- **1-10ms latency** instead of 10-100ms -- **Real-time access** for all consumers -- **Better resource utilization** - -### Operational Benefits -- **Concurrent debugging** while ROS runs -- **Multiple monitors** can watch Arduino simultaneously -- **No service interruption** for manual testing -- **Isolated failure domains** - -### Development Benefits -- **Easier debugging** with real-time access -- **Better testing workflows** -- **Scalable monitoring** architecture -- **Professional system management** - -## Migration Checklist - -### Pre-Migration -- [ ] Backup current serial communication scripts -- [ ] Test SOCAT installation and functionality -- [ ] Verify Arduino device permissions -- [ ] Document current system behavior - -### Migration Steps -- [ ] Implement SOCAT multiplexer script -- [ ] Update serial_ros_bridge.py to use control port -- [ ] Create new monitoring and debug scripts -- [ ] Update TMUX session management -- [ ] Test entire system integration - -### Post-Migration Testing -- [ ] Verify ROS bridge functionality -- [ ] Test concurrent monitoring -- [ ] Validate manual debug terminal -- [ ] Confirm system startup/shutdown -- [ ] Performance benchmarking - -This refactoring provides a robust, efficient, and scalable Arduino communication system that eliminates the current limitations while maintaining full compatibility with existing ROS operations. diff --git a/docs/TMUX_MODULAR_CONFIGURATION.md b/docs/TMUX_MODULAR_CONFIGURATION.md deleted file mode 100644 index 1486eab..0000000 --- a/docs/TMUX_MODULAR_CONFIGURATION.md +++ /dev/null @@ -1,225 +0,0 @@ -# Modular Tmux Session Manager - -The Hydrus tmux session manager has been refactored to use a dictionary-based configuration system, making it highly modular and customizable. - -## Features - -- **Dictionary-based configuration**: Each window is defined by a configuration dictionary -- **Flexible layouts**: Support for different tmux layouts per window -- **Command-line interface**: Various options for managing configurations -- **Configuration validation**: Ensure window configurations are properly structured -- **Selective window creation**: Create only specific windows -- **External configuration files**: Load configurations from JSON files - -## Configuration Structure - -Each window is defined by a dictionary with the following structure: - -```python -{ - "window_name": { - "window_index": 0, # Unique index for the window - "layout": "even-horizontal", # Tmux layout (optional) - "panes": [ - { - "name": "Pane Name", # Descriptive name - "command": "echo 'Hello'", # Command to run - "split": None # No split for first pane - }, - { - "name": "Second Pane", - "command": "htop", - "split": "horizontal" # Split direction: "horizontal" or "vertical" - } - ] - } -} -``` - -### Required Fields - -- `window_index`: Unique integer identifying the window order -- `panes`: List of pane configurations -- `panes[].command`: Command to execute in the pane -- `panes[].split`: Split direction for panes after the first (None for first pane) - -### Optional Fields - -- `layout`: Tmux layout name (default: "even-horizontal") -- `panes[].name`: Descriptive name for the pane (for documentation) - -## Usage Examples - -### Basic Usage -```bash -# Create all configured windows -python3 start_tmux_sessions.py - -# Create only specific windows -python3 start_tmux_sessions.py --windows "Controls" "Arduino" -``` - -### Configuration Management -```bash -# List all configured windows -python3 start_tmux_sessions.py --list-windows - -# Validate current configuration -python3 start_tmux_sessions.py --validate - -# Save current configuration to file -python3 start_tmux_sessions.py --save-config my_config.json - -# Load configuration from file -python3 start_tmux_sessions.py --config config/tmux_windows.json -``` - -### Development Workflow -```bash -# Create only development and monitoring windows -python3 start_tmux_sessions.py --windows "Development" "Arduino" - -# Load custom configuration for testing -python3 start_tmux_sessions.py --config test_config.json --windows "Controls" -``` - -## Available Layouts - -- `even-horizontal`: Panes evenly distributed horizontally -- `even-vertical`: Panes evenly distributed vertically -- `main-horizontal`: Large main pane on top, others below -- `main-vertical`: Large main pane on left, others on right -- `tiled`: Panes arranged in a tiled pattern - -## Pane Split Directions - -- `"horizontal"`: Split horizontally (new pane below) -- `"vertical"`: Split vertically (new pane to the right) -- `null`/`None`: No split (only for first pane) - -## Example Configurations - -### Simple Two-Pane Window -```json -{ - "Simple": { - "window_index": 0, - "layout": "even-horizontal", - "panes": [ - { - "name": "Main Terminal", - "command": "bash", - "split": null - }, - { - "name": "System Monitor", - "command": "htop", - "split": "horizontal" - } - ] - } -} -``` - -### Complex Four-Pane Development Window -```json -{ - "Development": { - "window_index": 1, - "layout": "tiled", - "panes": [ - { - "name": "Code Editor", - "command": "cd /workspace && vim", - "split": null - }, - { - "name": "Build Output", - "command": "cd /workspace && tail -f build.log", - "split": "horizontal" - }, - { - "name": "Git Status", - "command": "cd /workspace && watch git status", - "split": "vertical" - }, - { - "name": "Test Runner", - "command": "cd /workspace && pytest --watch", - "split": "horizontal" - } - ] - } -} -``` - -## Programming Interface - -You can also modify configurations programmatically: - -```python -from start_tmux_sessions import HydrusTmuxManager - -manager = HydrusTmuxManager() - -# Add a new window configuration -new_window = { - "window_index": 5, - "layout": "main-vertical", - "panes": [ - { - "name": "Main Task", - "command": "python3 my_script.py", - "split": None - }, - { - "name": "Monitor", - "command": "watch ps aux", - "split": "horizontal" - } - ] -} - -manager.add_window_config("Custom Window", new_window) - -# Validate the configuration -if manager.validate_window_config(new_window): - print("Configuration is valid!") - -# Create the session -manager.main() -``` - -## Migration from Old System - -The old individual window creation methods have been replaced with the modular system. If you need to customize windows: - -1. **Export current config**: `python3 start_tmux_sessions.py --save-config current.json` -2. **Edit the JSON file** with your customizations -3. **Load the config**: `python3 start_tmux_sessions.py --config current.json` - -## Troubleshooting - -### Configuration Validation Errors -```bash -# Check what's wrong with your configuration -python3 start_tmux_sessions.py --validate -``` - -Common issues: -- First pane has a split defined (should be `null`) -- Missing required fields (`command`, `split` for non-first panes) -- Invalid split directions (must be "horizontal" or "vertical") -- Duplicate window indices - -### Window Creation Failures -- Check that all script paths exist -- Verify ROS environment is properly sourced -- Ensure required dependencies are installed - -### Tmux Issues -- Make sure tmux is installed: `sudo apt install tmux` -- Check existing sessions: `tmux list-sessions` -- Kill problematic sessions: `tmux kill-session -t hydrus` - -This modular system provides much greater flexibility while maintaining compatibility with the existing Hydrus infrastructure. diff --git a/hocker b/hocker index 20b74cc..474d715 120000 --- a/hocker +++ b/hocker @@ -1 +1 @@ -/home/catkin_ws/src/hydrus-software-stack/docker/hydrus-docker/hocker.py \ No newline at end of file +/home/cesar/Projects/hydrus-software-stack/docker/hydrus-docker/hocker.py \ No newline at end of file diff --git a/hydrus-cli b/hydrus-cli index 3074f4a..03199d0 100755 --- a/hydrus-cli +++ b/hydrus-cli @@ -5,9 +5,6 @@ # Get the directory where this wrapper is located (should be project root) SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -# Set HYDRUS_ROOT environment variable to ensure test system works correctly -export HYDRUS_ROOT="$SCRIPT_DIR" - # Change to the project directory to ensure proper module resolution cd "$SCRIPT_DIR" diff --git a/hydrus_software_stack.egg-info/PKG-INFO b/hydrus_software_stack.egg-info/PKG-INFO deleted file mode 100644 index 93c0108..0000000 --- a/hydrus_software_stack.egg-info/PKG-INFO +++ /dev/null @@ -1,120 +0,0 @@ -Metadata-Version: 2.4 -Name: hydrus-software-stack -Version: 0.1.0 -Summary: Autonomous underwater vehicle software stack with computer vision and navigation capabilities -Home-page: https://github.com/your-username/hydrus-software-stack -Author: Cesar -Author-email: cesar@example.com -Project-URL: Bug Reports, https://github.com/your-username/hydrus-software-stack/issues -Project-URL: Source, https://github.com/your-username/hydrus-software-stack -Classifier: Development Status :: 3 - Alpha -Classifier: Intended Audience :: Developers -Classifier: License :: OSI Approved :: MIT License -Classifier: Programming Language :: Python :: 3 -Classifier: Programming Language :: Python :: 3.8 -Classifier: Programming Language :: Python :: 3.9 -Classifier: Programming Language :: Python :: 3.10 -Classifier: Programming Language :: Python :: 3.11 -Classifier: Operating System :: OS Independent -Requires-Python: >=3.8 -Description-Content-Type: text/markdown -License-File: LICENSE -Requires-Dist: opencv-python -Requires-Dist: numpy -Requires-Dist: matplotlib -Requires-Dist: requests -Requires-Dist: colorama -Requires-Dist: pyserial -Requires-Dist: fastapi -Requires-Dist: Flask -Requires-Dist: black>=23.0.0 -Requires-Dist: isort>=5.12.0 -Requires-Dist: flake8>=6.0.0 -Requires-Dist: pre-commit>=3.0.0 -Requires-Dist: mypy>=1.0.0 -Provides-Extra: depth-estimation -Requires-Dist: onnx; extra == "depth-estimation" -Requires-Dist: onnxruntime-gpu; extra == "depth-estimation" -Requires-Dist: onnxscript; extra == "depth-estimation" -Requires-Dist: onnxslim; extra == "depth-estimation" -Requires-Dist: torch; extra == "depth-estimation" -Requires-Dist: torchvision; extra == "depth-estimation" -Requires-Dist: tqdm; extra == "depth-estimation" -Requires-Dist: typer; extra == "depth-estimation" -Provides-Extra: all -Requires-Dist: onnx; extra == "all" -Requires-Dist: onnxruntime-gpu; extra == "all" -Requires-Dist: onnxscript; extra == "all" -Requires-Dist: onnxslim; extra == "all" -Requires-Dist: torch; extra == "all" -Requires-Dist: torchvision; extra == "all" -Requires-Dist: tqdm; extra == "all" -Requires-Dist: typer; extra == "all" -Dynamic: author -Dynamic: author-email -Dynamic: classifier -Dynamic: description -Dynamic: description-content-type -Dynamic: home-page -Dynamic: license-file -Dynamic: project-url -Dynamic: provides-extra -Dynamic: requires-dist -Dynamic: requires-python -Dynamic: summary - -
- -

๐ŸŒŠ Hydrus Software Stack

- -

- A comprehensive ROS-based toolkit for autonomous underwater vehicles. -
- Built for RobSub competitions with maintainability and usability at its core. -

- -

- Philosophy - ยท - Autonomy Docs - ยท - Issues - ยท - Docker Setup - ยท - GitHub -

- -Quick start: `./doctor.sh && ./docker/hydrus-docker/hocker` - -[![Tests](https://img.shields.io/badge/tests-passing-brightgreen.svg)](./run_tests.sh) -[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE) -[![ROS](https://img.shields.io/badge/ROS-Melodic-blue.svg)](http://wiki.ros.org/melodic) -[![Docker](https://img.shields.io/badge/Docker-Enabled-blue.svg)](docker/README.md) - -
- - -To start developing Hydrus ------- - -Hydrus is developed by [Rumarino Team](https://github.com/Rumarino-Team). We welcome both pull requests and issues on [GitHub](https://github.com/Rumarino-Team/hydrus-software-stack). - -* Read our [Philosophy & Contributing Guide](PHILOSOPHY.md) -* Check out the [autonomy system documentation](autonomy/README.md) -* Explore the [Docker deployment options](docker/README.md) -* Join discussions in [GitHub Issues](../../issues) - -Want to contribute? Check our [open issues](../../issues) and follow our [contribution guidelines](PHILOSOPHY.md). - -
-MIT Licensed - -Hydrus is released under the MIT license. Some parts of the software are released under other licenses as specified. - -Any user of this software shall indemnify and hold harmless the Rumarino Team and its members from and against all allegations, claims, actions, suits, demands, damages, liabilities, obligations, losses, settlements, judgments, costs and expenses which arise out of, relate to or result from any use of this software by user. - -**THIS IS ALPHA QUALITY SOFTWARE FOR RESEARCH AND COMPETITION PURPOSES ONLY. THIS IS NOT A PRODUCT. -YOU ARE RESPONSIBLE FOR COMPLYING WITH LOCAL LAWS AND REGULATIONS. -NO WARRANTY EXPRESSED OR IMPLIED.** -
diff --git a/hydrus_software_stack.egg-info/SOURCES.txt b/hydrus_software_stack.egg-info/SOURCES.txt deleted file mode 100644 index c0797ff..0000000 --- a/hydrus_software_stack.egg-info/SOURCES.txt +++ /dev/null @@ -1,45 +0,0 @@ -LICENSE -README.md -pyproject.toml -setup.py -autonomy/__init__.py -autonomy/arduino_simulator.py -autonomy/setup.py -autonomy/test_controller.py -autonomy/scripts/__init__.py -autonomy/scripts/profiler/__init__.py -autonomy/scripts/profiler/profiling_decorators.py -autonomy/scripts/profiler/profiling_test.py -autonomy/scripts/profiler/ros_profiler.py -autonomy/scripts/profiler/ros_profiler_demo.py -autonomy/scripts/web/__init__.py -autonomy/scripts/web/detection_viewer.py -autonomy/src/__init__.py -autonomy/src/api_server.py -autonomy/src/controllers.py -autonomy/src/custom_types.py -autonomy/src/cv_publishers.py -autonomy/src/mission_planner/__init__.py -autonomy/src/mission_planner/base_mission.py -autonomy/src/mission_planner/gate_mission.py -autonomy/src/mission_planner/gate_mission_tester.py -autonomy/src/mission_planner/gate_tester_visualizer.py -autonomy/src/mission_planner/mission_manager.py -autonomy/src/mission_planner/mission_tree.py -autonomy/src/mission_planner/prequalification_mission.py -autonomy/src/mission_planner/slalom_mission.py -autonomy/src/mission_planner/slalom_visualizer.py -autonomy/src/mission_planner/tagging_mission.py -autonomy/src/mission_planner/tagging_mission_test.py -autonomy/src/mission_planner/task_parameters.py -autonomy/src/mission_planner/test_gate_mission.py -autonomy/src/mission_planner/test_slalom.py -autonomy/src/mission_planner/test_slalom_integration.py -autonomy/src/mission_planner/types.py -hydrus_software_stack.egg-info/PKG-INFO -hydrus_software_stack.egg-info/SOURCES.txt -hydrus_software_stack.egg-info/dependency_links.txt -hydrus_software_stack.egg-info/entry_points.txt -hydrus_software_stack.egg-info/not-zip-safe -hydrus_software_stack.egg-info/requires.txt -hydrus_software_stack.egg-info/top_level.txt \ No newline at end of file diff --git a/hydrus_software_stack.egg-info/dependency_links.txt b/hydrus_software_stack.egg-info/dependency_links.txt deleted file mode 100644 index 8b13789..0000000 --- a/hydrus_software_stack.egg-info/dependency_links.txt +++ /dev/null @@ -1 +0,0 @@ - diff --git a/hydrus_software_stack.egg-info/entry_points.txt b/hydrus_software_stack.egg-info/entry_points.txt deleted file mode 100644 index f06db19..0000000 --- a/hydrus_software_stack.egg-info/entry_points.txt +++ /dev/null @@ -1,2 +0,0 @@ -[console_scripts] -hydrus-depth-estimator = autonomy.src.computer_vision.depth_estimation:main diff --git a/hydrus_software_stack.egg-info/not-zip-safe b/hydrus_software_stack.egg-info/not-zip-safe deleted file mode 100644 index 8b13789..0000000 --- a/hydrus_software_stack.egg-info/not-zip-safe +++ /dev/null @@ -1 +0,0 @@ - diff --git a/hydrus_software_stack.egg-info/requires.txt b/hydrus_software_stack.egg-info/requires.txt deleted file mode 100644 index 3af6dfa..0000000 --- a/hydrus_software_stack.egg-info/requires.txt +++ /dev/null @@ -1,33 +0,0 @@ -opencv-python -numpy -matplotlib -requests -colorama -pyserial -fastapi -Flask -black>=23.0.0 -isort>=5.12.0 -flake8>=6.0.0 -pre-commit>=3.0.0 -mypy>=1.0.0 - -[all] -onnx -onnxruntime-gpu -onnxscript -onnxslim -torch -torchvision -tqdm -typer - -[depth-estimation] -onnx -onnxruntime-gpu -onnxscript -onnxslim -torch -torchvision -tqdm -typer diff --git a/hydrus_software_stack.egg-info/top_level.txt b/hydrus_software_stack.egg-info/top_level.txt deleted file mode 100644 index 342156a..0000000 --- a/hydrus_software_stack.egg-info/top_level.txt +++ /dev/null @@ -1 +0,0 @@ -autonomy diff --git a/pyproject.toml b/pyproject.toml index e32b941..cf0a375 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,46 +1,6 @@ -[tool.black] -line-length = 88 -target-version = ['py38'] -include = '\.pyi?$' -extend-exclude = ''' -/( - # directories - \.eggs - | \.git - | \.hg - | \.mypy_cache - | \.tox - | \.venv - | _build - | buck-out - | build - | dist -)/ -''' - -[tool.isort] -profile = "black" -multi_line_output = 3 -line_length = 88 -known_first_party = ["autonomy", "mission_planner", "computer_vision"] -known_third_party = ["rospy", "cv2", "numpy", "matplotlib", "flask", "fastapi"] - -[tool.mypy] -python_version = "3.8" -warn_return_any = true -warn_unused_configs = true -disallow_untyped_defs = true -ignore_missing_imports = true - -[tool.flake8] -max-line-length = 88 -extend-ignore = ["E203", "W503"] -exclude = [ - ".git", - "__pycache__", - "docs/source/conf.py", - "old", - "build", - "dist", - ".venv" +[tool.uv.workspace] +members = [ + "src/computer_vision", + "tools/hydrus_cli", + "autonomy", ] diff --git a/scripts/__init__.py b/scripts/__init__.py deleted file mode 100644 index 7994ee4..0000000 --- a/scripts/__init__.py +++ /dev/null @@ -1 +0,0 @@ -# Scripts package diff --git a/scripts/activate_formatters.sh b/scripts/activate_formatters.sh deleted file mode 100755 index ed89f1c..0000000 --- a/scripts/activate_formatters.sh +++ /dev/null @@ -1,10 +0,0 @@ -#!/bin/bash -# Quick activation script for formatter virtual environment -echo "๐Ÿ”Œ Activating formatter virtual environment..." -source .venv-formatters/bin/activate -echo "โœ… Formatter environment activated!" -echo "๐Ÿ’ก Available commands:" -echo " - black . --line-length 88 --target-version py38" -echo " - isort . --profile black" -echo " - flake8 ." -echo " - pre-commit run --all-files" diff --git a/scripts/commands/dev.py b/scripts/commands/dev.py deleted file mode 100644 index 7761bba..0000000 --- a/scripts/commands/dev.py +++ /dev/null @@ -1,4 +0,0 @@ -# All the dev tools should be in here. -# For example formatting tools, linters, etc. -# I would like to have an internall interactive tool that lets you have -# multiple devs tools enabled accessible from the cli. diff --git a/scripts/commands/ros.py b/scripts/commands/ros.py deleted file mode 100644 index dc22164..0000000 --- a/scripts/commands/ros.py +++ /dev/null @@ -1,567 +0,0 @@ -""" -ROS command module for Hydrus CLI -Handles ROS workspace operations, camera management, and rosbag utilities -""" - -import os -import re -import subprocess -import sys -from pathlib import Path -from typing import Dict, List, Optional - -import typer - -from .utils import get_building_path - -ros_app = typer.Typer() - - -class HydrusRosManager: - def __init__(self, volume: bool = False): - self.volume = volume - self.workspace_dir = get_building_path(self.volume) - self.rosbags_dir = self.workspace_dir / "src/hydrus-software-stack/rosbags" - - # Create rosbags directory if it doesn't exist - self.rosbags_dir.mkdir(parents=True, exist_ok=True) - - # Constants for download - self.DEFAULT_ROSBAG_URL = "https://drive.google.com/file/d/16Lr-CbW1rW6rKh8_mWClTQMIjm2u0y8X/view?usp=drive_link" - - def _run_command( - self, - cmd: List[str], - check: bool = True, - capture_output: bool = False, - env: Optional[Dict] = None, - cwd: Optional[Path] = None, - ) -> subprocess.CompletedProcess: - """Run a command with proper error handling""" - if env is None: - env = os.environ.copy() - - cmd_str = " ".join(cmd) - print(f"๐Ÿ”ง Executing: {cmd_str}") - - try: - return subprocess.run( - cmd, - check=check, - capture_output=capture_output, - env=env, - cwd=cwd, - text=True, - ) - except subprocess.CalledProcessError as e: - if check: - print(f"โŒ Command failed: {cmd_str}") - print(f" Exit code: {e.returncode}") - if capture_output and e.stderr: - print(f" Error: {e.stderr}") - raise - except FileNotFoundError: - print(f"โŒ Command not found: {cmd_str}") - raise - - def _setup_environment(self) -> Dict[str, str]: - """Setup ROS environment""" - env = os.environ.copy() - - # Source devel/setup.bash equivalent - setup_file = self.workspace_dir / "devel/setup.bash" - if setup_file.exists(): - # Add workspace paths to environment - env[ - "ROS_PACKAGE_PATH" - ] = f"{self.workspace_dir}/src:{env.get('ROS_PACKAGE_PATH', '')}" - env[ - "CMAKE_PREFIX_PATH" - ] = f"{self.workspace_dir}/devel:{env.get('CMAKE_PREFIX_PATH', '')}" - env[ - "LD_LIBRARY_PATH" - ] = f"{self.workspace_dir}/devel/lib:{env.get('LD_LIBRARY_PATH', '')}" - env[ - "PYTHONPATH" - ] = f"{self.workspace_dir}/devel/lib/python3/dist-packages:{env.get('PYTHONPATH', '')}" - - return env - - def build_workspace(self, clean: bool = False): - """Build the catkin workspace.""" - print("๐Ÿ”จ Building catkin workspace...") - - if not self.workspace_dir.exists(): - print(f"โŒ Workspace directory not found: {self.workspace_dir}") - return False - - env = self._setup_environment() - - # Clean if requested - if clean: - print("๐Ÿงน Cleaning workspace...") - clean_cmd = ["catkin_make", "clean"] - try: - self._run_command( - clean_cmd, cwd=self.workspace_dir, env=env, check=False - ) - except subprocess.CalledProcessError: - print("Warning: Clean failed, but continuing...") - - # Build command that sources ROS environment first - ros_distro = env.get("ROS_DISTRO", "noetic") - cmake_command = f"source /opt/ros/{ros_distro}/setup.bash && catkin_make --cmake-args -DCMAKE_BUILD_TYPE=Release -DPYTHON_EXECUTABLE=/usr/bin/python3 -DPYTHON_INCLUDE_DIR=/usr/include/python3.9 -DPYTHON_LIBRARY=/usr/lib/aarch64-linux-gnu/libpython3.9.so" - - cmake_args = ["bash", "-c", cmake_command] - - try: - self._run_command(cmake_args, cwd=self.workspace_dir, env=env) - print("โœ… Workspace built successfully") - return True - except subprocess.CalledProcessError as e: - print(f"โŒ Build failed: {e}") - return False - - def run_camera_entrypoint(self, zed_option: bool = False): - """Run the camera entrypoint.""" - print("๐Ÿ“ท Starting ZED camera entrypoint...") - print(f"ZED_OPTION: {zed_option}") - - env = self._setup_environment() - env["ZED_OPTION"] = "true" if zed_option else "false" - - try: - # Build workspace first - if not self.build_workspace(): - print("โŒ Failed to build workspace") - return False - - # Launch ZED camera - if zed_option: - print("Launching ZED2i camera with RViz display...") - launch_cmd = ["roslaunch", "zed_display_rviz", "display_zed2i.launch"] - else: - print("Launching ZED2i camera...") - launch_cmd = ["roslaunch", "--wait", "zed_wrapper", "zed2i.launch"] - - self._run_command(launch_cmd, env=env, cwd=self.workspace_dir) - return True - - except subprocess.CalledProcessError as e: - print(f"โŒ Failed to launch ZED camera: {e}") - return False - except KeyboardInterrupt: - print("\n๐Ÿ›‘ Camera entrypoint stopped by user") - return True - except Exception as e: - print(f"โŒ Unexpected error: {e}") - return False - - def _extract_file_id(self, url: str) -> Optional[str]: - """Extract the file ID from a Google Drive URL""" - pattern = r"drive\.google\.com/file/d/([a-zA-Z0-9_-]+)" - match = re.search(pattern, url) - if match: - return match.group(1) - return None - - def _install_download_dependencies(self) -> bool: - """Install required dependencies for downloading""" - try: - print("Installing gdown and requests...") - self._run_command( - [sys.executable, "-m", "pip", "install", "gdown", "requests"] - ) - print("โœ… Dependencies installed successfully!") - return True - except subprocess.CalledProcessError: - print("โŒ Failed to install dependencies. Please install them manually:") - print("pip install gdown requests") - print("\nOr if you have permission issues, try:") - print("pip install --user gdown requests") - return False - - def _check_rosbags_exist(self) -> bool: - """Check if any .bag files exist in the rosbags directory""" - bag_files = list(self.rosbags_dir.glob("*.bag")) - return len(bag_files) > 0 - - def download_ros_bags(self, url: Optional[str] = None, force: bool = False): - """Download ROS bags.""" - print("๐Ÿ“ฅ Checking for ROS bag files...") - - if not force and self._check_rosbags_exist(): - print("โœ… ROS bag files already exist in the rosbags directory.") - existing_bags = list(self.rosbags_dir.glob("*.bag")) - print("Existing bag files:") - for bag in existing_bags: - print(f" โ€ข {bag.name}") - return True - - if not force: - print("No ROS bag files found.") - - download_url = url or self.DEFAULT_ROSBAG_URL - - # Install dependencies - if not self._install_download_dependencies(): - return False - - print(f"๐Ÿ“ฅ Downloading rosbag from {download_url}") - print("This may take a few minutes depending on your internet connection...") - - # Import gdown here to avoid import error if not installed - try: - import gdown - except ImportError: - print("โŒ Error: gdown is not available. Please run the installation first.") - return False - - file_id = self._extract_file_id(download_url) - if not file_id: - print("โŒ Error: Could not extract file ID from the URL.") - return False - - output_path = self.rosbags_dir / "zed2i_camera.bag" - - try: - # Use gdown to download from Google Drive - gdown.download(id=file_id, output=str(output_path), quiet=False) - - if output_path.exists() and output_path.stat().st_size > 0: - print(f"โœ… Successfully downloaded rosbag to {output_path}") - return True - else: - print("โŒ Error: Download failed or file is empty.") - return False - except Exception as e: - print(f"โŒ Error downloading file: {e}") - return False - - def video_to_ros_bag( - self, - video_path: str, - output_bag_path: Optional[str] = None, - target_fps: float = 10.0, - include_camera_info: bool = False, - rgb_topic: str = "/zed2i/zed_node/rgb/image_rect_color", - camera_info_topic: str = "/zed2i/zed_node/rgb/camera_info", - frame_id: str = "zed2i_left_camera_optical_frame", - ): - """Convert video files to ROS bags.""" - print("๐ŸŽฌ Converting video to ROS bag...") - print(f"Input video: {video_path}") - - video_path_obj = Path(video_path) - if not video_path_obj.exists(): - print(f"โŒ Error: Video file not found: {video_path}") - return False - - # Set default output path if not provided - if output_bag_path is None: - output_bag_path = str(self.rosbags_dir / f"{video_path_obj.stem}.bag") - - print(f"Output bag: {output_bag_path}") - print(f"Target FPS: {target_fps}") - print(f"Include camera info: {include_camera_info}") - - try: - import cv2 - import rosbag - import rospy - except ImportError as e: - print(f"โŒ Error: Required dependencies not found: {e}") - print("Please install: pip install opencv-python") - print("And ensure ROS packages are available in the environment") - return False - - # Open the video file - cap = cv2.VideoCapture(video_path) - if not cap.isOpened(): - print(f"โŒ Error: Could not open video file: {video_path}") - return False - - # Get video properties - original_fps = cap.get(cv2.CAP_PROP_FPS) - total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT)) - width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH)) - height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT)) - - print("Video properties:") - print(f" โ€ข Resolution: {width}x{height}") - print(f" โ€ข Original FPS: {original_fps}") - print(f" โ€ข Total frames: {total_frames}") - - # Calculate frame skip for target FPS - frame_skip = max(1, int(original_fps / target_fps)) - duration_seconds = total_frames / original_fps - expected_output_frames = int(duration_seconds * target_fps) - - print("Conversion settings:") - print(f" โ€ข Frame skip: {frame_skip}") - print(f" โ€ข Expected output frames: {expected_output_frames}") - - try: - with rosbag.Bag(output_bag_path, "w") as bag: - frame_count = 0 - written_frames = 0 - - # Create camera info message if requested - camera_info = None - if include_camera_info: - camera_info = self._create_camera_info(width, height, frame_id) - - while True: - ret, frame = cap.read() - if not ret: - break - - # Skip frames to achieve target FPS - if frame_count % frame_skip != 0: - frame_count += 1 - continue - - # Calculate timestamp - timestamp = rospy.Time.from_sec(written_frames / target_fps) - - # Convert OpenCV image to ROS Image message - ros_image = self._cv2_to_ros_image(frame, "bgr8", frame_id) - ros_image.header.stamp = timestamp - - # Write image message to bag - bag.write(rgb_topic, ros_image, timestamp) - - # Write camera info if requested - if camera_info: - camera_info.header.stamp = timestamp - bag.write(camera_info_topic, camera_info, timestamp) - - written_frames += 1 - frame_count += 1 - - # Progress update - if written_frames % 50 == 0: - progress = (frame_count / total_frames) * 100 - print( - f"Progress: {progress:.1f}% ({written_frames} frames written)" - ) - - cap.release() - - print("โœ… Conversion completed successfully!") - print(f" โ€ข Output file: {output_bag_path}") - print(f" โ€ข Frames written: {written_frames}") - print(f" โ€ข Duration: {written_frames / target_fps:.2f} seconds") - return True - - except Exception as e: - print(f"โŒ Error during conversion: {e}") - cap.release() - return False - - def _cv2_to_ros_image( - self, cv_image, encoding: str = "bgr8", frame_id: str = "camera_link" - ): - """Convert an OpenCV image to a ROS sensor_msgs/Image message.""" - from sensor_msgs.msg import Image - - ros_image = Image() - ros_image.header.frame_id = frame_id - ros_image.height, ros_image.width = cv_image.shape[:2] - ros_image.encoding = encoding - ros_image.is_bigendian = False - ros_image.step = ( - cv_image.strides[0] - if cv_image.strides - else cv_image.shape[1] - * cv_image.itemsize - * (cv_image.shape[2] if len(cv_image.shape) > 2 else 1) - ) - ros_image.data = cv_image.tobytes() - return ros_image - - def _create_camera_info( - self, width: int, height: int, frame_id: str = "camera_link" - ): - """Create a basic CameraInfo message with reasonable defaults.""" - from sensor_msgs.msg import CameraInfo - - camera_info = CameraInfo() - camera_info.header.frame_id = frame_id - camera_info.width = width - camera_info.height = height - - # Basic camera parameters (these are rough estimates) - fx = fy = width * 0.8 # Rough focal length estimate - cx = width / 2.0 # Principal point x - cy = height / 2.0 # Principal point y - - camera_info.K = [fx, 0, cx, 0, fy, cy, 0, 0, 1] - camera_info.D = [0, 0, 0, 0, 0] # No distortion - camera_info.R = [1, 0, 0, 0, 1, 0, 0, 0, 1] - camera_info.P = [fx, 0, cx, 0, 0, fy, cy, 0, 0, 0, 1, 0] - camera_info.distortion_model = "plumb_bob" - - return camera_info - - -# === Typer Commands === - - -@ros_app.command("build") -def build_workspace_cmd( - volume: bool = typer.Option(False, "--volume", "-v", help="Use volume directory"), - clean: bool = typer.Option( - False, "--clean", "-c", help="Clean workspace before building" - ), -): - """Build the catkin workspace.""" - typer.echo("๐Ÿ”จ Building ROS workspace...") - - manager = HydrusRosManager(volume=volume) - success = manager.build_workspace(clean=clean) - - if success: - typer.echo("โœ… Workspace built successfully!") - raise typer.Exit(0) - else: - typer.echo("โŒ Workspace build failed!") - raise typer.Exit(1) - - -@ros_app.command("camera") -def run_camera_cmd( - volume: bool = typer.Option(False, "--volume", "-v", help="Use volume directory"), - rviz: bool = typer.Option( - False, "--rviz", "-r", help="Launch camera with RViz display" - ), -): - """Run the ZED camera entrypoint.""" - typer.echo("๐Ÿ“ท Starting ZED camera...") - - manager = HydrusRosManager(volume=volume) - success = manager.run_camera_entrypoint(zed_option=rviz) - - if success: - typer.echo("โœ… Camera stopped successfully!") - raise typer.Exit(0) - else: - typer.echo("โŒ Camera failed!") - raise typer.Exit(1) - - -@ros_app.command("download-bags") -def download_bags_cmd( - volume: bool = typer.Option(False, "--volume", "-v", help="Use volume directory"), - url: Optional[str] = typer.Option(None, "--url", "-u", help="Custom download URL"), - force: bool = typer.Option( - False, "--force", "-f", help="Force download even if bags exist" - ), -): - """Download ROS bag files.""" - typer.echo("๐Ÿ“ฅ Downloading ROS bags...") - - manager = HydrusRosManager(volume=volume) - success = manager.download_ros_bags(url=url, force=force) - - if success: - typer.echo("โœ… ROS bags downloaded successfully!") - raise typer.Exit(0) - else: - typer.echo("โŒ ROS bags download failed!") - raise typer.Exit(1) - - -@ros_app.command("video-to-bag") -def video_to_bag_cmd( - video_path: str = typer.Argument(..., help="Path to input video file"), - output_path: Optional[str] = typer.Option( - None, "--output", "-o", help="Output bag file path" - ), - volume: bool = typer.Option(False, "--volume", "-v", help="Use volume directory"), - fps: float = typer.Option(10.0, "--fps", "-f", help="Target FPS for output bag"), - camera_info: bool = typer.Option( - False, "--camera-info", "-c", help="Include camera info messages" - ), - rgb_topic: str = typer.Option( - "/zed2i/zed_node/rgb/image_rect_color", "--rgb-topic", help="RGB image topic" - ), - info_topic: str = typer.Option( - "/zed2i/zed_node/rgb/camera_info", "--info-topic", help="Camera info topic" - ), - frame_id: str = typer.Option( - "zed2i_left_camera_optical_frame", "--frame-id", help="Frame ID for messages" - ), -): - """Convert video file to ROS bag.""" - typer.echo(f"๐ŸŽฌ Converting video {video_path} to ROS bag...") - - manager = HydrusRosManager(volume=volume) - success = manager.video_to_ros_bag( - video_path=video_path, - output_bag_path=output_path, - target_fps=fps, - include_camera_info=camera_info, - rgb_topic=rgb_topic, - camera_info_topic=info_topic, - frame_id=frame_id, - ) - - if success: - typer.echo("โœ… Video converted to ROS bag successfully!") - raise typer.Exit(0) - else: - typer.echo("โŒ Video conversion failed!") - raise typer.Exit(1) - - -@ros_app.command("list-bags") -def list_bags_cmd( - volume: bool = typer.Option(False, "--volume", "-v", help="Use volume directory") -): - """List available ROS bag files.""" - manager = HydrusRosManager(volume=volume) - - bag_files = list(manager.rosbags_dir.glob("*.bag")) - - if bag_files: - typer.echo("๐Ÿ“‹ Available ROS bag files:") - for bag in bag_files: - size_mb = bag.stat().st_size / (1024 * 1024) - typer.echo(f" โ€ข {bag.name} ({size_mb:.1f} MB)") - else: - typer.echo("๐Ÿ“‹ No ROS bag files found.") - typer.echo(f"Directory: {manager.rosbags_dir}") - - -@ros_app.command("info") -def ros_info_cmd( - volume: bool = typer.Option(False, "--volume", "-v", help="Use volume directory") -): - """Show ROS workspace information.""" - manager = HydrusRosManager(volume=volume) - - typer.echo("๐Ÿ“Š ROS Workspace Information:") - typer.echo(f" โ€ข Workspace path: {manager.workspace_dir}") - typer.echo(f" โ€ข ROSbags directory: {manager.rosbags_dir}") - - # Check if workspace exists - if manager.workspace_dir.exists(): - typer.echo(" โ€ข Workspace: โœ… Found") - - # Check if built - devel_dir = manager.workspace_dir / "devel" - if devel_dir.exists(): - typer.echo(" โ€ข Built: โœ… Yes") - else: - typer.echo(" โ€ข Built: โŒ No") - else: - typer.echo(" โ€ข Workspace: โŒ Not found") - - # Check bag files - bag_count = len(list(manager.rosbags_dir.glob("*.bag"))) - typer.echo(f" โ€ข ROS bag files: {bag_count}") - - -if __name__ == "__main__": - ros_app() diff --git a/scripts/commands/runner.py b/scripts/commands/runner.py deleted file mode 100644 index 1494d36..0000000 --- a/scripts/commands/runner.py +++ /dev/null @@ -1,2 +0,0 @@ -# Runner should be used to run scripts that are in the repository. -# Runner could have a smart breakpoint checker diff --git a/scripts/commands/test/__init__.py b/scripts/commands/test/__init__.py deleted file mode 100644 index 3ce1e0d..0000000 --- a/scripts/commands/test/__init__.py +++ /dev/null @@ -1,13 +0,0 @@ -""" -Test command module for Hydrus software stack. - -This module contains all test-related functionality including: -- Test execution and management -- Test configuration loading from distributed .hss files -- Test logging and result archiving -- Test caching functionality -""" - -from .test import test_app - -__all__ = ["test_app"] diff --git a/scripts/format.sh b/scripts/format.sh deleted file mode 100755 index 66477bc..0000000 --- a/scripts/format.sh +++ /dev/null @@ -1,44 +0,0 @@ -#!/bin/bash -# Python Code Formatting Script for Hydrus Software Stack - -echo "๐Ÿ”ง Running Python code formatters..." - -# Check if we're in the right directory -if [ ! -f "requirements.txt" ]; then - echo "โŒ Error: Please run this script from the project root directory" - exit 1 -fi - -# Activate virtual environment if it exists, otherwise try system packages -if [ -d ".venv" ]; then - echo "๐Ÿ”ง Activating virtual environment..." - source .venv/bin/activate -else - echo "โš ๏ธ Virtual environment not found, trying system packages..." - echo "๐Ÿ’ก Run './setup_formatting.sh' to set up the virtual environment" -fi - -# Check if tools are available -if ! command -v black &> /dev/null; then - echo "โŒ Black not found. Please run './setup_formatting.sh' first" - exit 1 -fi - -echo "๐ŸŽจ Formatting Python code with Black..." -black . --line-length 88 --target-version py38 - -echo "๐Ÿ“ Sorting imports with isort..." -isort . --profile black - -echo "๐Ÿ” Running flake8 linting..." -flake8 . --max-line-length=88 --extend-ignore=E203,W503 - -echo "โœ… Code formatting complete!" -echo "" -echo "๐Ÿ“‹ Summary:" -echo " - Black: Code formatted to 88 character lines" -echo " - isort: Import statements sorted and organized" -echo " - flake8: Linting checks completed" -echo "" -echo "๐Ÿ’ก To set up automatic formatting on git commits, run:" -echo " pre-commit install" diff --git a/scripts/format_with_venv.sh b/scripts/format_with_venv.sh deleted file mode 100755 index 19257e0..0000000 --- a/scripts/format_with_venv.sh +++ /dev/null @@ -1,26 +0,0 @@ -#!/bin/bash -# Format script using the dedicated virtual environment -set -e - -echo "๐Ÿ”ง Running Python formatters..." - -# Check if virtual environment exists -if [ ! -d ".venv-formatters" ]; then - echo "โŒ Virtual environment not found. Run './setup_venv_formatters.sh' first" - exit 1 -fi - -# Activate virtual environment -source .venv-formatters/bin/activate - -# Run formatters -echo "๐ŸŽจ Formatting with Black..." -black . --line-length 88 --target-version py38 - -echo "๐Ÿ“ Sorting imports with isort..." -isort . --profile black - -echo "๐Ÿ” Running flake8 linting..." -flake8 . --statistics - -echo "โœ… Code formatting complete!" diff --git a/scripts/install_ros2_jassy.sh b/scripts/install_ros2_jassy.sh deleted file mode 100644 index 6f651c2..0000000 --- a/scripts/install_ros2_jassy.sh +++ /dev/null @@ -1,39 +0,0 @@ -#!/bin/bash -set -e - -ROS_DISTRO=jazzy -UBU_CODENAME=$(lsb_release -cs) - -echo "๐Ÿ›  Installing ROS 2 $ROS_DISTRO on Ubuntu $UBU_CODENAME..." - -# Locale -sudo apt update && sudo apt install -y locales -sudo locale-gen en_US en_US.UTF-8 -sudo update-locale LC_ALL=en_US.UTF-8 LANG=en_US.UTF-8 -export LANG=en_US.UTF-8 - -# Repos & keys -sudo apt install -y software-properties-common curl gnupg -sudo add-apt-repository universe -y -sudo curl -sSL https://raw.githubusercontent.com/ros/rosdistro/master/ros.key \ - -o /usr/share/keyrings/ros-archive-keyring.gpg -echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/ros-archive-keyring.gpg] \ - http://packages.ros.org/ros2/ubuntu $UBU_CODENAME main" | \ - sudo tee /etc/apt/sources.list.d/ros2.list > /dev/null - -# Install -sudo apt update -sudo apt install -y ros-$ROS_DISTRO-desktop \ - python3-colcon-common-extensions python3-pip python3-rosdep - -# Rosdep init -sudo rosdep init || true -rosdep update - -# Source on startup -if ! grep -F "source /opt/ros/$ROS_DISTRO/setup.bash" ~/.bashrc >/dev/null; then - echo "source /opt/ros/$ROS_DISTRO/setup.bash" >> ~/.bashrc -fi -source ~/.bashrc - -echo "โœ… ROS 2 $ROS_DISTRO installation complete!" diff --git a/scripts/jetson.py b/scripts/jetson.py deleted file mode 100755 index 60518ed..0000000 --- a/scripts/jetson.py +++ /dev/null @@ -1,383 +0,0 @@ -#!/usr/bin/env python3 -""" -Hydrus Jetson Deployment Script -Replaces jetson.sh with improved modularity and error handling -""" - -import os -import platform -import subprocess -import sys -from pathlib import Path -from typing import Dict, List, Optional - - -class JetsonDeploymentManager: - def __init__(self): - self.network_name = "ros-network" - self.ros_master_uri = "http://ros-master:11311" - self.use_qemu = False - self.qemu_args = [] - self.zed_option = False - self.deploy = True - - # Detect architecture and setup QEMU if needed - self._setup_architecture() - - def _run_command( - self, - cmd: List[str], - check: bool = True, - capture_output: bool = False, - env: Optional[Dict] = None, - ) -> subprocess.CompletedProcess: - """Run a command with proper error handling""" - if env is None: - env = os.environ.copy() - - try: - return subprocess.run( - cmd, check=check, capture_output=capture_output, env=env, text=True - ) - except subprocess.CalledProcessError as e: - if check: - print(f"Command failed: {' '.join(cmd)}") - print(f"Exit code: {e.returncode}") - if capture_output and e.stderr: - print(f"Error: {e.stderr}") - raise - - def _setup_architecture(self): - """Detect system architecture and set up QEMU if needed""" - arch = platform.machine() - - if arch == "x86_64": - print("Detected x86_64 architecture. Will use QEMU for ARM emulation.") - - # Check if QEMU is installed - try: - self._run_command( - ["qemu-system-aarch64", "--version"], capture_output=True - ) - except (subprocess.CalledProcessError, FileNotFoundError): - print("ERROR: QEMU is not installed. Please install it with:") - print( - " sudo apt-get install qemu-system-arm qemu-efi qemu-user-static" - ) - sys.exit(1) - - # Register QEMU binary formats if not already done - binfmt_file = Path("/proc/sys/fs/binfmt_misc/qemu-aarch64") - if not binfmt_file.exists(): - print("Setting up QEMU binary formats for ARM emulation...") - try: - self._run_command( - [ - "docker", - "run", - "--rm", - "--privileged", - "multiarch/qemu-user-static", - "--reset", - "-p", - "yes", - ] - ) - except subprocess.CalledProcessError as e: - print(f"Failed to setup QEMU binary formats: {e}") - sys.exit(1) - - self.use_qemu = True - self.qemu_args = ["--platform", "linux/arm64"] - print("QEMU emulation enabled for ARM containers.") - - def _create_docker_network(self): - """Create custom Docker network if it doesn't exist""" - try: - # Check if network exists - result = self._run_command( - ["docker", "network", "ls", "--format", "{{.Name}}"], - capture_output=True, - ) - - if self.network_name not in result.stdout: - print(f"Creating custom Docker network: {self.network_name}") - self._run_command(["docker", "network", "create", self.network_name]) - else: - print(f"Docker network {self.network_name} already exists") - - except subprocess.CalledProcessError as e: - print(f"Failed to create Docker network: {e}") - sys.exit(1) - - def _container_exists(self, container_name: str) -> bool: - """Check if a container exists""" - try: - result = self._run_command( - ["docker", "ps", "-a", "--format", "{{.Names}}"], capture_output=True - ) - return container_name in result.stdout.split("\n") - except subprocess.CalledProcessError: - return False - - def _container_running(self, container_name: str) -> bool: - """Check if a container is running""" - try: - result = self._run_command( - ["docker", "ps", "--format", "{{.Names}}"], capture_output=True - ) - return container_name in result.stdout.split("\n") - except subprocess.CalledProcessError: - return False - - def _start_ros_master(self): - """Run the ROS master container""" - print("=" * 50) - print("Step 1: Setting up ROS master container") - print("=" * 50) - - if self._container_exists("ros-master"): - if self._container_running("ros-master"): - print("ROS master is already running.") - else: - print("Starting existing ROS master container...") - self._run_command(["docker", "start", "ros-master"]) - else: - print("Creating and starting ROS master container...") - cmd = [ - "docker", - "run", - "-d", - "--name", - "ros-master", - "--network", - self.network_name, - "-p", - "11311:11311", - "ros:melodic-ros-core", - "stdbuf", - "-o", - "L", - "roscore", - ] - self._run_command(cmd) - - # Wait for ROS master to be up - print("Waiting for ROS master to start...") - import time - - time.sleep(3) - - def _build_and_run_zed_camera(self): - """Build and run the ZED camera container""" - print("=" * 50) - print("Step 2: Setting up ZED camera container") - print("=" * 50) - - dockerfile_path = "docker/jetson/camera.Dockerfile" - - # Build ZED camera container - if self.use_qemu: - print("Building ZED camera container with QEMU emulation...") - build_cmd = ( - ["docker", "build"] - + self.qemu_args - + ["-t", "zed-camera", "-f", dockerfile_path, "."] - ) - else: - print("Building ZED camera container...") - build_cmd = [ - "docker", - "build", - "-t", - "zed-camera", - "-f", - dockerfile_path, - ".", - ] - - self._run_command(build_cmd) - - # Run ZED camera container - if self._container_exists("zed-camera"): - if self._container_running("zed-camera"): - print("ZED camera container is already running.") - else: - print("Starting existing ZED camera container...") - self._run_command(["docker", "start", "zed-camera"]) - else: - print("Creating and starting ZED camera container...") - - env_vars = { - "ROS_MASTER_URI": "http://ros-master:11311", - "ZED_OPTION": str(self.zed_option).lower(), - } - - cmd = [ - "docker", - "run", - "-d", - "--name", - "zed-camera", - "--network", - self.network_name, - "--privileged", - "--gpus", - "all", - ] - - # Add environment variables - for key, value in env_vars.items(): - cmd.extend(["--env", f"{key}={value}"]) - - # Add QEMU args if needed - if self.use_qemu: - cmd.extend(self.qemu_args) - - cmd.append("zed-camera") - self._run_command(cmd) - - # Wait for ZED camera to be ready - print("Waiting for ZED camera to start...") - import time - - time.sleep(3) - - def _build_and_run_hydrus(self): - """Build and run the Hydrus container""" - print("=" * 50) - print("Step 3: Setting up Hydrus container") - print("=" * 50) - - dockerfile_path = "docker/jetson/hydrus.Dockerfile" - - # Check if Hydrus image exists - try: - result = self._run_command( - [ - "docker", - "images", - "hydrus:latest", - "--format", - "{{.Repository}}:{{.Tag}}", - ], - capture_output=True, - ) - - if "hydrus:latest" not in result.stdout: - print("Building Hydrus image...") - if self.use_qemu: - build_cmd = ( - ["docker", "build"] - + self.qemu_args - + ["-t", "hydrus:latest", "-f", dockerfile_path, "."] - ) - else: - build_cmd = [ - "docker", - "build", - "-t", - "hydrus:latest", - "-f", - dockerfile_path, - ".", - ] - - self._run_command(build_cmd) - else: - print("Hydrus image already exists, skipping build.") - except subprocess.CalledProcessError: - print("Error checking Hydrus image, building anyway...") - if self.use_qemu: - build_cmd = ( - ["docker", "build"] - + self.qemu_args - + ["-t", "hydrus:latest", "-f", dockerfile_path, "."] - ) - else: - build_cmd = [ - "docker", - "build", - "-t", - "hydrus:latest", - "-f", - dockerfile_path, - ".", - ] - - self._run_command(build_cmd) - - # Run Hydrus container - if self._container_exists("hydrus"): - if self._container_running("hydrus"): - print("Hydrus container is already running.") - else: - print("Starting existing Hydrus container...") - self._run_command(["docker", "start", "hydrus"]) - else: - print("Creating and starting Hydrus container...") - - env_vars = { - "ROS_MASTER_URI": "http://ros-master:11311", - "ARDUINO_BOARD": "arduino:avr:mega", - "DEPLOY": str(self.deploy).lower(), - } - - cmd = [ - "docker", - "run", - "-d", - "--name", - "hydrus", - "--network", - self.network_name, - "--privileged", - "--gpus", - "all", - "-p", - "8000:8000", - "--device", - "/dev/ttyACM0:/dev/ttyACM0", - ] - - # Add environment variables - for key, value in env_vars.items(): - cmd.extend(["--env", f"{key}={value}"]) - - # Add QEMU args if needed - if self.use_qemu: - cmd.extend(self.qemu_args) - - cmd.extend(["-it", "hydrus:latest"]) - self._run_command(cmd) - - def main(self): - """Main execution function""" - print("Starting Jetson deployment...") - - # Set environment variables - os.environ["ROS_MASTER_URI"] = self.ros_master_uri - - try: - # Create Docker network - self._create_docker_network() - - # Start ROS master - self._start_ros_master() - - # Build and run ZED camera - self._build_and_run_zed_camera() - - # Build and run Hydrus - self._build_and_run_hydrus() - - print(f"Containers are up and running on network '{self.network_name}'!") - - except Exception as e: - print(f"Deployment failed: {e}") - sys.exit(1) - - -if __name__ == "__main__": - manager = JetsonDeploymentManager() - manager.main() diff --git a/scripts/setup_formatting.sh b/scripts/setup_formatting.sh deleted file mode 100755 index ed048a0..0000000 --- a/scripts/setup_formatting.sh +++ /dev/null @@ -1,45 +0,0 @@ -#!/bin/bash -# Setup Script for Python Code Formatting Tools - -echo "๐Ÿš€ Setting up Python code formatting for Hydrus Software Stack..." - -# Check if we need to install python3-venv -if ! python3 -c "import venv" 2>/dev/null; then - echo "๐Ÿ“ฆ Installing python3-venv package..." - sudo apt update && sudo apt install -y python3-venv python3-pip -fi - -# Create virtual environment if it doesn't exist -if [ ! -d ".venv" ]; then - echo "๐Ÿ”ง Creating Python virtual environment..." - python3 -m venv .venv -fi - -# Activate virtual environment and install dependencies -echo "๐Ÿ“ฆ Installing formatting dependencies in virtual environment..." -source .venv/bin/activate -pip install --upgrade pip -pip install black isort flake8 pre-commit mypy - -# Setup pre-commit hooks -echo "๐Ÿ”— Setting up pre-commit hooks..." -pre-commit install - -echo "๐ŸŽฏ Running initial format on existing code..." -# Run formatting with virtual environment -source .venv/bin/activate && ./format.sh - -echo "โœ… Setup complete!" -echo "" -echo "๐ŸŽ‰ Your Python formatting tools are now configured:" -echo " โ€ข Black: Automatic code formatting" -echo " โ€ข isort: Import statement organization" -echo " โ€ข flake8: Code linting and style checking" -echo " โ€ข pre-commit: Automatic formatting before commits" -echo "" -echo "๐Ÿ“ Usage:" -echo " โ€ข Run './format.sh' to format all Python files" -echo " โ€ข Formatting will run automatically on git commits" -echo " โ€ข Use 'source .venv/bin/activate && pre-commit run --all-files' to check all files" -echo "" -echo "๐Ÿ’ก Note: The tools are installed in a virtual environment (.venv)" diff --git a/scripts/setup_venv_formatters.sh b/scripts/setup_venv_formatters.sh deleted file mode 100755 index fa00f9f..0000000 --- a/scripts/setup_venv_formatters.sh +++ /dev/null @@ -1,120 +0,0 @@ -#!/bin/bash -# Virtual Environment Setup Script for Python Formatters -# This script creates a virtual environment and installs the latest available formatting tools - -set -e # Exit on any error - -echo "๐Ÿš€ Setting up virtual environment with Python formatters..." - -# Script configuration -VENV_NAME=".venv-formatters" -PYTHON_VERSION="python3" - -# Check if Python is available -if ! command -v $PYTHON_VERSION &> /dev/null; then - echo "โŒ Error: $PYTHON_VERSION is not installed or not in PATH" - exit 1 -fi - -# Remove existing virtual environment if it exists -if [ -d "$VENV_NAME" ]; then - echo "๐Ÿ—‘๏ธ Removing existing virtual environment..." - rm -rf "$VENV_NAME" -fi - -# Create virtual environment -echo "๐Ÿ”ง Creating virtual environment: $VENV_NAME" -$PYTHON_VERSION -m venv "$VENV_NAME" - -# Activate virtual environment -echo "๐Ÿ”Œ Activating virtual environment..." -source "$VENV_NAME/bin/activate" - -# Upgrade pip -echo "๐Ÿ“ฆ Upgrading pip..." -python -m pip install --upgrade pip - -# Install formatting tools with latest compatible versions -echo "๐ŸŽจ Installing Python formatting tools..." -echo " - Installing Black (Python code formatter)..." -pip install "black>=24.0.0,<25.0.0" - -echo " - Installing isort (Import sorter)..." -pip install "isort>=5.12.0,<7.0.0" - -echo " - Installing flake8 (Linter)..." -pip install "flake8>=6.0.0,<8.0.0" - -echo " - Installing pre-commit (Git hooks)..." -pip install "pre-commit>=3.0.0,<4.0.0" - -echo " - Installing mypy (Type checker)..." -pip install "mypy>=1.0.0,<2.0.0" - -# Show installed versions -echo "" -echo "๐Ÿ“‹ Installed formatter versions:" -pip list | grep -E "(black|isort|flake8|pre-commit|mypy)" || echo "No formatters found" - -# Create activation script -cat > activate_formatters.sh << 'EOF' -#!/bin/bash -# Quick activation script for formatter virtual environment -echo "๐Ÿ”Œ Activating formatter virtual environment..." -source .venv-formatters/bin/activate -echo "โœ… Formatter environment activated!" -echo "๐Ÿ’ก Available commands:" -echo " - black . --line-length 88 --target-version py38" -echo " - isort . --profile black" -echo " - flake8 ." -echo " - pre-commit run --all-files" -EOF - -chmod +x activate_formatters.sh - -# Create a simple format script that uses this venv -cat > format_with_venv.sh << 'EOF' -#!/bin/bash -# Format script using the dedicated virtual environment -set -e - -echo "๐Ÿ”ง Running Python formatters..." - -# Check if virtual environment exists -if [ ! -d ".venv-formatters" ]; then - echo "โŒ Virtual environment not found. Run './setup_venv_formatters.sh' first" - exit 1 -fi - -# Activate virtual environment -source .venv-formatters/bin/activate - -# Run formatters -echo "๐ŸŽจ Formatting with Black..." -black . --line-length 88 --target-version py38 - -echo "๐Ÿ“ Sorting imports with isort..." -isort . --profile black - -echo "๐Ÿ” Running flake8 linting..." -flake8 . --statistics - -echo "โœ… Code formatting complete!" -EOF - -chmod +x format_with_venv.sh - -echo "" -echo "โœ… Virtual environment setup complete!" -echo "" -echo "๐Ÿ“ Created files:" -echo " - $VENV_NAME/ (virtual environment)" -echo " - activate_formatters.sh (quick activation)" -echo " - format_with_venv.sh (formatting script)" -echo "" -echo "๐Ÿš€ Usage:" -echo " 1. To activate: source activate_formatters.sh" -echo " 2. To format code: ./format_with_venv.sh" -echo " 3. To install pre-commit hooks: source activate_formatters.sh && pre-commit install" -echo "" -echo "๐Ÿ’ก This environment uses the latest publicly available versions of formatting tools" diff --git a/scripts/tests.hss b/scripts/tests.hss deleted file mode 100644 index 8285e4a..0000000 --- a/scripts/tests.hss +++ /dev/null @@ -1,36 +0,0 @@ -# Script Tests Configuration -# Tests for running script nodes indefinitely -name: "Script Tests" -description: "Tests for long-running script nodes" -strategy: "script" - -tests: - - name: "API Server Script" - path: "../autonomy/src/api_server.py" - args: [] - timeout: 2 - infinite_loop: true - description: "API server script test" - strategy: "ScriptTestStrategy" - enabled: true - test_type: "script" - - - name: "Controllers Script" - path: "../autonomy/src/controllers.py" - args: [] - timeout: 2 - infinite_loop: true - description: "Controllers node script test" - strategy: "ScriptTestStrategy" - enabled: true - test_type: "script" - - - name: "CV Publishers Script" - path: "../autonomy/src/cv_publishers.py" - args: [] - timeout: 2 - infinite_loop: true - description: "Computer vision publishers script test" - strategy: "ScriptTestStrategy" - enabled: true - test_type: "script" diff --git a/src/computer_vision/.python-version b/src/computer_vision/.python-version new file mode 100644 index 0000000..e4fba21 --- /dev/null +++ b/src/computer_vision/.python-version @@ -0,0 +1 @@ +3.12 diff --git a/src/computer_vision/README.md b/src/computer_vision/README.md new file mode 100644 index 0000000..80bc204 --- /dev/null +++ b/src/computer_vision/README.md @@ -0,0 +1,4 @@ +Python Library for running the Computer Vision Algorithms. + + +How to compile this proyect \ No newline at end of file diff --git a/src/computer_vision/__init__.py b/src/computer_vision/__init__.py new file mode 100644 index 0000000..55febd7 --- /dev/null +++ b/src/computer_vision/__init__.py @@ -0,0 +1,36 @@ +""" +Computer Vision Library for Hydrus + +This package provides computer vision functionality including: +- YOLO object detection +- Color filtering +- Depth estimation +- SLAM capabilities +- 3D point calculations +""" + +from .custom_types import Point3D, Rotation3D, Detection, Detections +from .detection_core import ( + ColorFilterConfig, + YOLOModelManager, + DetectionPipelineManager +) +from .depth_estimation import DepthEstimator +from .slam import MonocularSLAM + +__version__ = "0.1.0" + +__all__ = [ + # Types + "Point3D", + "Rotation3D", + "Detection", + "Detections", + # Detection Core + "ColorFilterConfig", + "YOLOModelManager", + "DetectionPipelineManager", + # Other modules + "DepthEstimator", + "MonocularSLAM", +] diff --git a/autonomy/scripts/cv/color_filters.py b/src/computer_vision/examples/color_filters.py similarity index 100% rename from autonomy/scripts/cv/color_filters.py rename to src/computer_vision/examples/color_filters.py diff --git a/autonomy/scripts/cv/distance.py b/src/computer_vision/examples/distance.py similarity index 100% rename from autonomy/scripts/cv/distance.py rename to src/computer_vision/examples/distance.py diff --git a/autonomy/scripts/cv/object_detector.py b/src/computer_vision/examples/object_detector.py similarity index 100% rename from autonomy/scripts/cv/object_detector.py rename to src/computer_vision/examples/object_detector.py diff --git a/autonomy/scripts/cv/orbs.py b/src/computer_vision/examples/orbs.py similarity index 100% rename from autonomy/scripts/cv/orbs.py rename to src/computer_vision/examples/orbs.py diff --git a/autonomy/scripts/cv/plane_detection.py b/src/computer_vision/examples/plane_detection.py similarity index 100% rename from autonomy/scripts/cv/plane_detection.py rename to src/computer_vision/examples/plane_detection.py diff --git a/autonomy/scripts/cv/slam_visuation.py b/src/computer_vision/examples/slam_visuation.py similarity index 100% rename from autonomy/scripts/cv/slam_visuation.py rename to src/computer_vision/examples/slam_visuation.py diff --git a/src/computer_vision/examples/yolo11n.pt b/src/computer_vision/examples/yolo11n.pt new file mode 100644 index 0000000..45b273b Binary files /dev/null and b/src/computer_vision/examples/yolo11n.pt differ diff --git a/src/computer_vision/main.py b/src/computer_vision/main.py new file mode 100644 index 0000000..184fc4b --- /dev/null +++ b/src/computer_vision/main.py @@ -0,0 +1,6 @@ +def main(): + print("Hello from computer-vision!") + + +if __name__ == "__main__": + main() diff --git a/src/computer_vision/pyproject.toml b/src/computer_vision/pyproject.toml new file mode 100644 index 0000000..5858907 --- /dev/null +++ b/src/computer_vision/pyproject.toml @@ -0,0 +1,18 @@ +[project] +name = "computer-vision" +version = "0.1.0" +description = "Computer vision library for Hydrus autonomous underwater vehicle" +readme = "README.md" +requires-python = ">=3.12" +dependencies = [ + "numpy>=2.3.2", + "opencv-python>=4.11.0.86", + "ultralytics>=8.3.182", +] + +[build-system] +requires = ["hatchling"] +build-backend = "hatchling.build" + +[tool.hatch.build.targets.wheel] +packages = ["computer_vision"] diff --git a/src/godot_sim/.editorconfig b/src/godot_sim/.editorconfig deleted file mode 100644 index f28239b..0000000 --- a/src/godot_sim/.editorconfig +++ /dev/null @@ -1,4 +0,0 @@ -root = true - -[*] -charset = utf-8 diff --git a/src/godot_sim/.gitattributes b/src/godot_sim/.gitattributes deleted file mode 100644 index 8ad74f7..0000000 --- a/src/godot_sim/.gitattributes +++ /dev/null @@ -1,2 +0,0 @@ -# Normalize EOL for all files that Git considers text files. -* text=auto eol=lf diff --git a/src/godot_sim/.gitignore b/src/godot_sim/.gitignore deleted file mode 100644 index 0af181c..0000000 --- a/src/godot_sim/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -# Godot 4+ specific ignores -.godot/ -/android/ diff --git a/src/godot_sim/icon.svg b/src/godot_sim/icon.svg deleted file mode 100644 index c6bbb7d..0000000 --- a/src/godot_sim/icon.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/src/godot_sim/icon.svg.import b/src/godot_sim/icon.svg.import deleted file mode 100644 index 13cc3f6..0000000 --- a/src/godot_sim/icon.svg.import +++ /dev/null @@ -1,37 +0,0 @@ -[remap] - -importer="texture" -type="CompressedTexture2D" -uid="uid://bxjcyvekp7q4n" -path="res://.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.ctex" -metadata={ -"vram_texture": false -} - -[deps] - -source_file="res://icon.svg" -dest_files=["res://.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.ctex"] - -[params] - -compress/mode=0 -compress/high_quality=false -compress/lossy_quality=0.7 -compress/hdr_compression=1 -compress/normal_map=0 -compress/channel_pack=0 -mipmaps/generate=false -mipmaps/limit=-1 -roughness/mode=0 -roughness/src_normal="" -process/fix_alpha_border=true -process/premult_alpha=false -process/normal_map_invert_y=false -process/hdr_as_srgb=false -process/hdr_clamp_exposure=false -process/size_limit=0 -detect_3d/compress_to=1 -svg/scale=1.0 -editor/scale_with_editor_scale=false -editor/convert_colors_with_editor_theme=false diff --git a/src/godot_sim/project.godot b/src/godot_sim/project.godot deleted file mode 100644 index 512b5de..0000000 --- a/src/godot_sim/project.godot +++ /dev/null @@ -1,15 +0,0 @@ -; Engine configuration file. -; It's best edited using the editor UI and not directly, -; since the parameters that go here are not all obvious. -; -; Format: -; [section] ; section goes between [] -; param=value ; assign values to parameters - -config_version=5 - -[application] - -config/name="godot_sim" -config/features=PackedStringArray("4.4", "Forward Plus") -config/icon="res://icon.svg" diff --git a/src/godot_sim/scripts/depth_camera.gd b/src/godot_sim/scripts/depth_camera.gd deleted file mode 100644 index 38890e9..0000000 --- a/src/godot_sim/scripts/depth_camera.gd +++ /dev/null @@ -1,100 +0,0 @@ -extends Camera - -# Depth camera properties -export var max_depth := 10.0 # Maximum depth in meters -export var min_depth := 0.1 # Minimum depth in meters -export var noise_amount := 0.02 # Noise in depth measurements - -# Output resolution -export var depth_width := 320 -export var depth_height := 240 - -# Internal variables -var depth_texture: ImageTexture -var depth_image: Image - -func _ready(): - # Initialize depth image - depth_image = Image.new() - depth_image.create(depth_width, depth_height, false, Image.FORMAT_RF) - - depth_texture = ImageTexture.new() - depth_texture.create_from_image(depth_image) - - print("Depth camera initialized") - -func update_depth_image(): - # This would normally be done with a shader, but this is a simplified version - # In an actual implementation, you'd use a depth pre-pass render - - # Get the current viewport (needed for raycasting) - var space_state = get_world().direct_space_state - - # Calculate step size - var x_step = 1.0 / depth_width - var y_step = 1.0 / depth_height - - # Iterate through the image grid - for y in range(depth_height): - for x in range(depth_width): - # Calculate normalized device coordinates (-1 to 1) - var ndc_x = (x / float(depth_width)) * 2.0 - 1.0 - var ndc_y = (y / float(depth_height)) * 2.0 - 1.0 - - # Cast ray from camera - var from = global_transform.origin - var to = project_position(Vector2(ndc_x, ndc_y), max_depth) - - # Perform raycast - var result = space_state.intersect_ray(from, to) - - var depth_value: float - if result and result.has("position"): - # Calculate distance from camera to hit point - depth_value = from.distance_to(result.position) - - # Apply some noise to simulate sensor noise - depth_value += rand_range(-noise_amount, noise_amount) - - # Clamp to valid range - depth_value = clamp(depth_value, min_depth, max_depth) - else: - # No hit, use max depth - depth_value = max_depth - - # Normalize depth to 0-1 range for visualization - var normalized_depth = depth_value / max_depth - - # Set pixel in depth image - depth_image.lock() - depth_image.set_pixel(x, y, Color(depth_value, 0, 0)) - depth_image.unlock() - - # Update texture - depth_texture.create_from_image(depth_image) - -func get_depth_image() -> Image: - return depth_image.duplicate() - -func get_depth_texture() -> ImageTexture: - return depth_texture - -func get_depth_array() -> PoolRealArray: - # Creates a flat array of depth values (for ROS) - var array = PoolRealArray() - array.resize(depth_width * depth_height) - - depth_image.lock() - - for y in range(depth_height): - for x in range(depth_width): - var color = depth_image.get_pixel(x, y) - array[y * depth_width + x] = color.r # Depth value stored in red channel - - depth_image.unlock() - - return array - -func _process(_delta): - # Update depth image periodically (could be set to lower frequency) - update_depth_image() diff --git a/src/godot_sim/scripts/depth_camera.gd.uid b/src/godot_sim/scripts/depth_camera.gd.uid deleted file mode 100644 index 8294ab9..0000000 --- a/src/godot_sim/scripts/depth_camera.gd.uid +++ /dev/null @@ -1 +0,0 @@ -uid://cl3q5ug3lus1q diff --git a/src/godot_sim/scripts/detection_target.gd b/src/godot_sim/scripts/detection_target.gd deleted file mode 100644 index d906af5..0000000 --- a/src/godot_sim/scripts/detection_target.gd +++ /dev/null @@ -1,92 +0,0 @@ -extends Spatial - -# Target properties -export var target_type := "buoy" # Type identifier (buoy, gate, bin, etc) -export var target_color := Color(1.0, 0.0, 0.0, 1.0) # Default: red -export var target_scale := 1.0 -export var target_id := 0 # Unique ID -export var is_moving := false -export var movement_speed := 0.1 -export var movement_range := Vector3(1, 0.5, 1) - -# Movement variables -var initial_position := Vector3.ZERO -var movement_time := 0.0 - -# Visibility from cameras -onready var mesh_instance = $MeshInstance -onready var collision_shape = $CollisionShape - -func _ready(): - # Store initial position for movement - initial_position = global_transform.origin - - # Set up visual appearance - if mesh_instance: - var material = SpatialMaterial.new() - material.albedo_color = target_color - material.metallic = 0.2 - material.roughness = 0.7 - material.emission_enabled = true - material.emission = target_color - material.emission_energy = 0.2 - mesh_instance.set_surface_material(0, material) - - # Apply scale - mesh_instance.scale = Vector3(target_scale, target_scale, target_scale) - - if collision_shape: - collision_shape.scale = mesh_instance.scale - -func _process(delta): - if is_moving: - # Simple oscillating movement - movement_time += delta - - var x_offset = sin(movement_time * 0.5) * movement_range.x - var y_offset = sin(movement_time * 0.7) * movement_range.y - var z_offset = cos(movement_time * 0.3) * movement_range.z - - global_transform.origin = initial_position + Vector3(x_offset, y_offset, z_offset) - -func get_detection_info(): - # Return information about this target for detection systems - return { - "type": target_type, - "color": { - "r": target_color.r, - "g": target_color.g, - "b": target_color.b - }, - "position": { - "x": global_transform.origin.x, - "y": global_transform.origin.y, - "z": global_transform.origin.z - }, - "scale": target_scale, - "id": target_id - } - -func highlight(): - # Visual feedback when detected - if mesh_instance: - var material = mesh_instance.get_surface_material(0) - if material: - material.emission_energy = 1.0 - - # Create a tween to reset emission - var tween = Tween.new() - add_child(tween) - tween.interpolate_property(material, "emission_energy", - 1.0, 0.2, 0.5, - Tween.TRANS_QUAD, Tween.EASE_OUT) - tween.start() - - yield(tween, "tween_completed") - tween.queue_free() - -func _on_Area_body_entered(body): - # Called when a body enters the detection area - if body.has_method("target_detected"): - body.target_detected(self) - highlight() diff --git a/src/godot_sim/scripts/detection_target.gd.uid b/src/godot_sim/scripts/detection_target.gd.uid deleted file mode 100644 index 03f87f3..0000000 --- a/src/godot_sim/scripts/detection_target.gd.uid +++ /dev/null @@ -1 +0,0 @@ -uid://47tc01dyknh6 diff --git a/src/godot_sim/scripts/main_scene.gd b/src/godot_sim/scripts/main_scene.gd deleted file mode 100644 index d0f19be..0000000 --- a/src/godot_sim/scripts/main_scene.gd +++ /dev/null @@ -1,99 +0,0 @@ -extends Spatial - -# Main scene controller for submarine simulation - -# References to key nodes -onready var submarine = $Submarine -onready var water_environment = $WaterEnvironment -onready var target_container = $Targets - -# Target scenes for spawning -export(PackedScene) var buoy_scene -export(PackedScene) var gate_scene -export(PackedScene) var bin_scene - -# Configuration properties -export var enable_websocket := true -export var enable_target_spawning := true -export var num_targets := 3 -export var target_spacing := 3.0 -export var simulation_name := "Hydrus Submarine Simulation" - -func _ready(): - OS.set_window_title(simulation_name) - print("Starting " + simulation_name) - - if enable_target_spawning: - spawn_targets() - - # Connect signals - if submarine: - submarine.connect("target_detected", self, "_on_submarine_target_detected") - -func spawn_targets(): - if not target_container or not buoy_scene: - print("Can't spawn targets: missing target container or scene") - return - - print("Spawning targets...") - - # Spawn buoys in a pattern - var colors = [ - Color(1.0, 0.0, 0.0), # Red - Color(0.0, 1.0, 0.0), # Green - Color(0.0, 0.0, 1.0), # Blue - Color(1.0, 1.0, 0.0), # Yellow - Color(1.0, 0.0, 1.0) # Purple - ] - - # Create a grid of targets - var grid_size = ceil(sqrt(num_targets)) - var index = 0 - - for i in range(grid_size): - for j in range(grid_size): - if index >= num_targets: - break - - var position = Vector3( - (i - grid_size/2) * target_spacing, - -1.0 - randf() * 2.0, - (j - grid_size/2) * target_spacing - ) - - # Create the target - var target = buoy_scene.instance() - target_container.add_child(target) - - # Configure the target - target.global_transform.origin = position - target.target_color = colors[index % colors.size()] - target.target_id = index - target.is_moving = (randf() > 0.5) - - index += 1 - - print("Spawned " + str(index) + " targets") - -func _on_submarine_target_detected(target): - # Called when submarine detects a target - print("Submarine detected target: " + str(target.get_detection_info())) - -func _process(_delta): - # Update UI if needed - pass - -func _input(event): - # Handle key events - if event is InputEventKey and event.pressed: - match event.scancode: - KEY_ESCAPE: - # Quit simulation - get_tree().quit() - KEY_R: - # Reset submarine position - if submarine: - submarine.global_transform.origin = Vector3(0, -2, 0) - submarine.rotation = Vector3.ZERO - submarine.linear_velocity = Vector3.ZERO - submarine.angular_velocity = Vector3.ZERO diff --git a/src/godot_sim/scripts/main_scene.gd.uid b/src/godot_sim/scripts/main_scene.gd.uid deleted file mode 100644 index fcb71e3..0000000 --- a/src/godot_sim/scripts/main_scene.gd.uid +++ /dev/null @@ -1 +0,0 @@ -uid://b1k8u0hau2xsp diff --git a/src/godot_sim/scripts/submarine_controller.gd b/src/godot_sim/scripts/submarine_controller.gd deleted file mode 100644 index 78873c3..0000000 --- a/src/godot_sim/scripts/submarine_controller.gd +++ /dev/null @@ -1,330 +0,0 @@ -extends RigidBody - -# WebSocket connection variables -var ws := WebSocketClient.new() -var ws_connected := false -var connection_url := "ws://localhost:8000/ws" - -# Submarine physical properties -export var buoyancy_factor := 1.0 -export var water_drag := 0.1 -export var water_angular_drag := 0.3 -export var max_thrust := 100.0 -export var max_torque := 20.0 - -# Thruster configuration -var thrusters = [] -var current_cmd_vel = {"linear": Vector3.ZERO, "angular": Vector3.ZERO} - -# Camera reference -onready var camera = $Camera -onready var depth_camera = $DepthCamera - -# Timers for data transmission -var state_timer := 0.0 -var camera_timer := 0.0 -var STATE_INTERVAL := 0.05 # 20 Hz -var CAMERA_INTERVAL := 0.1 # 10 Hz - -class Thruster: - var position: Vector3 # Position relative to center of mass - var direction: Vector3 # Thrust direction vector (normalized) - var max_force: float - var current_value: float = 0.0 # Current PWM value (normalized -1 to 1) - - func _init(pos: Vector3, dir: Vector3, max_f: float): - position = pos - direction = dir.normalized() - max_force = max_f - - func set_value(pwm_value: float): - # Convert PWM (-255 to 255) to normalized value (-1 to 1) - current_value = clamp(pwm_value / 255.0, -1.0, 1.0) - - func get_force() -> Vector3: - return direction * current_value * max_force - - func get_torque(center_of_mass: Vector3) -> Vector3: - var force = get_force() - var lever = position - center_of_mass - return lever.cross(force) - -func _ready(): - # Initialize physics - set_use_custom_integrator(true) - set_max_contacts_reported(4) - set_contact_monitor(true) - - # Initialize thrusters (position, direction, max force) - setup_thrusters() - - # Initialize WebSocket connection - setup_websocket() - - # Set up camera - if camera: - camera.set_current(true) - - # Initial notification - print("Submarine controller initialized") - -func setup_thrusters(): - # This configuration depends on your submarine's actual thruster layout - # Example of 8 thrusters: 4 vertical, 4 horizontal - - # Horizontal thrusters (X-configuration) - thrusters.append(Thruster.new(Vector3(0.3, 0, 0.3), Vector3(1, 0, -1), max_thrust)) - thrusters.append(Thruster.new(Vector3(-0.3, 0, 0.3), Vector3(-1, 0, -1), max_thrust)) - thrusters.append(Thruster.new(Vector3(-0.3, 0, -0.3), Vector3(-1, 0, 1), max_thrust)) - thrusters.append(Thruster.new(Vector3(0.3, 0, -0.3), Vector3(1, 0, 1), max_thrust)) - - # Vertical thrusters - thrusters.append(Thruster.new(Vector3(0.25, 0.1, 0.25), Vector3(0, 1, 0), max_thrust)) - thrusters.append(Thruster.new(Vector3(-0.25, 0.1, 0.25), Vector3(0, 1, 0), max_thrust)) - thrusters.append(Thruster.new(Vector3(-0.25, 0.1, -0.25), Vector3(0, 1, 0), max_thrust)) - thrusters.append(Thruster.new(Vector3(0.25, 0.1, -0.25), Vector3(0, 1, 0), max_thrust)) - -func setup_websocket(): - # Connect WebSocket signals - ws.connect("connection_established", self, "_on_connection_established") - ws.connect("connection_error", self, "_on_connection_error") - ws.connect("connection_closed", self, "_on_connection_closed") - ws.connect("data_received", self, "_on_data_received") - - # Start connection - var err = ws.connect_to_url(connection_url) - if err != OK: - print("Unable to connect to WebSocket server: ", err) - else: - print("Connecting to WebSocket server: " + connection_url) - -func _on_connection_established(protocol): - ws_connected = true - print("WebSocket connection established with protocol: ", protocol) - # Send initial log message - send_log_message("Submarine simulation connected") - -func _on_connection_error(): - ws_connected = false - print("WebSocket connection error") - -func _on_connection_closed(was_clean = false): - ws_connected = false - print("WebSocket connection closed, clean: ", was_clean) - - # Try to reconnect after a delay - yield(get_tree().create_timer(2.0), "timeout") - var err = ws.connect_to_url(connection_url) - if err != OK: - print("Unable to reconnect to WebSocket server: ", err) - -func _on_data_received(): - # Process incoming data from ROS - var data = ws.get_peer(1).get_packet().get_string_from_utf8() - - # Parse JSON data - var json = JSON.parse(data) - if json.error != OK: - print("JSON parse error: ", json.error) - return - - var payload = json.result - - # Handle different types of messages - if payload.has("cmd_vel"): - handle_cmd_vel(payload.cmd_vel) - elif payload.has("thruster"): - handle_thruster(payload.thruster) - elif payload.has("depth_control"): - handle_depth_control(payload.depth_control) - elif payload.has("torpedo_control"): - handle_torpedo_control(payload.torpedo_control) - -func handle_cmd_vel(cmd_vel): - # Store current command velocity - current_cmd_vel.linear.x = cmd_vel.linear.x - current_cmd_vel.linear.y = cmd_vel.linear.y - current_cmd_vel.linear.z = cmd_vel.linear.z - current_cmd_vel.angular.x = cmd_vel.angular.x - current_cmd_vel.angular.y = cmd_vel.angular.y - current_cmd_vel.angular.z = cmd_vel.angular.z - - # Convert cmd_vel to thruster values (simplified) - # In a real system, this would use a thrust allocation algorithm - -func handle_thruster(thruster_data): - # Set individual thruster values - var index = thruster_data.index - 1 # Convert 1-based to 0-based - if index >= 0 and index < thrusters.size(): - thrusters[index].set_value(thruster_data.value) - -func handle_depth_control(depth_pwm): - # Apply depth control to vertical thrusters - var value = float(depth_pwm) - for i in range(4, 8): # Assuming thrusters 4-7 are vertical - thrusters[i].set_value(value) - -func handle_torpedo_control(torpedo_pwm): - # Not implemented: could trigger torpedo launch animation - pass - -func _integrate_forces(state): - # Apply buoyancy - apply_buoyancy(state) - - # Apply water drag - apply_water_drag(state) - - # Apply thruster forces - apply_thruster_forces(state) - -func apply_buoyancy(state): - # Simple buoyancy calculation - var depth = -global_transform.origin.y # Y is up in Godot - var buoyancy_force = Vector3(0, buoyancy_factor, 0) - - # Apply more buoyancy when deeper - if depth > 0: - buoyancy_force.y += depth * 2.0 - - state.add_central_force(buoyancy_force) - -func apply_water_drag(state): - # Apply linear drag - var velocity = state.linear_velocity - var drag_force = -velocity * velocity.length() * water_drag - state.add_central_force(drag_force) - - # Apply angular drag - var angular_velocity = state.angular_velocity - var angular_drag = -angular_velocity * angular_velocity.length() * water_angular_drag - state.add_torque(angular_drag) - -func apply_thruster_forces(state): - var center_of_mass = state.get_center_of_mass() - - for thruster in thrusters: - # Apply force - var force = thruster.get_force() - state.add_force(force, thruster.position - center_of_mass) - -func _process(delta): - # Process WebSocket connection - if ws_connected: - ws.poll() - - # Send state updates at regular intervals - state_timer += delta - if state_timer >= STATE_INTERVAL and ws_connected: - state_timer = 0 - send_state() - - # Send camera images at regular intervals - camera_timer += delta - if camera_timer >= CAMERA_INTERVAL and ws_connected: - camera_timer = 0 - send_camera_image() - send_depth_image() - -func _physics_process(delta): - # Apply forces from cmd_vel (simplified) - # In real system, this would use the cmd_vel to calculate thruster values - pass - -func send_state(): - # Send submarine state to ROS - var state = { - "pos": [ - global_transform.origin.x, - global_transform.origin.y, - global_transform.origin.z - ], - "orient": [ - global_transform.basis.get_rotation_quat().x, - global_transform.basis.get_rotation_quat().y, - global_transform.basis.get_rotation_quat().z, - global_transform.basis.get_rotation_quat().w - ], - "vel": [ - linear_velocity.x, - linear_velocity.y, - linear_velocity.z - ], - "ang_vel": [ - angular_velocity.x, - angular_velocity.y, - angular_velocity.z - ] - } - - var json_data = {"state": state} - ws.get_peer(1).put_packet(JSON.print(json_data).to_utf8()) - -func send_camera_image(): - if !camera: - return - - # Capture viewport texture - var viewport = camera.get_viewport() - var img = viewport.get_texture().get_data() - img.flip_y() # Godot renders upside down compared to OpenCV convention - - # Convert to RGB format - img.convert(Image.FORMAT_RGB8) - - # Encode to base64 - var base64_img = Marshalls.raw_to_base64(img.get_data()) - - # Create camera data packet - var camera_data = { - "width": img.get_width(), - "height": img.get_height(), - "encoding": "rgb8", - "data": base64_img - } - - # Send camera data - var json_data = {"camera": camera_data} - ws.get_peer(1).put_packet(JSON.print(json_data).to_utf8()) - -func send_depth_image(): - if !depth_camera: - return - - # This would capture depth data in a real implementation - # For now, we'll simulate a depth image with distance data - var width = 320 - var height = 240 - - # Create fake depth data (in a real implementation, this would come from the depth camera) - var depth_data = PoolByteArray() - for y in height: - for x in width: - # Create simple depth value (increasing from left to right) - var depth_value = 1.0 + (float(x) / width * 5.0) - - # Convert float to bytes - var bytes = var2bytes(depth_value) - for b in bytes: - depth_data.append(b) - - # Encode to base64 - var base64_depth = Marshalls.raw_to_base64(depth_data) - - # Create depth data packet - var depth_payload = { - "width": width, - "height": height, - "encoding": "32FC1", - "data": base64_depth - } - - # Send depth data - var json_data = {"depth": depth_payload} - ws.get_peer(1).put_packet(JSON.print(json_data).to_utf8()) - -func send_log_message(message): - # Send a log message to ROS - var json_data = {"log": message} - if ws_connected: - ws.get_peer(1).put_packet(JSON.print(json_data).to_utf8()) diff --git a/src/godot_sim/scripts/submarine_controller.gd.uid b/src/godot_sim/scripts/submarine_controller.gd.uid deleted file mode 100644 index 53d8233..0000000 --- a/src/godot_sim/scripts/submarine_controller.gd.uid +++ /dev/null @@ -1 +0,0 @@ -uid://ct8jrxfxnfvw7 diff --git a/src/godot_sim/scripts/water_environment.gd b/src/godot_sim/scripts/water_environment.gd deleted file mode 100644 index c215ecc..0000000 --- a/src/godot_sim/scripts/water_environment.gd +++ /dev/null @@ -1,173 +0,0 @@ -extends Spatial - -# Water environment properties -export var water_color := Color(0.1, 0.4, 0.8, 0.8) -export var water_depth := 10.0 -export var water_clarity := 0.5 # 0-1, affects visibility distance - -# Lighting properties -export var ambient_light_color := Color(0.05, 0.15, 0.3, 1.0) -export var directional_light_color := Color(0.8, 0.8, 1.0, 1.0) -export var directional_light_energy := 0.6 - -# References to scene nodes -onready var water_surface = $WaterSurface -onready var water_volume = $WaterVolume -onready var directional_light = $DirectionalLight -onready var ambient_light = $AmbientLight - -# Objects in the environment -var objects = [] -var targets = [] - -func _ready(): - # Initialize water properties - if water_surface: - water_surface.get_surface_material(0).albedo_color = water_color - - if water_volume: - var volume_material = water_volume.get_surface_material(0) - if volume_material: - volume_material.albedo_color = Color(water_color.r, water_color.g, water_color.b, 0.1) - - # Initialize lighting - if ambient_light: - ambient_light.light_color = ambient_light_color - - if directional_light: - directional_light.light_color = directional_light_color - directional_light.light_energy = directional_light_energy - - # Apply water effects to cameras in the scene - setup_underwater_effects() - - # Add collision detection for water volume - add_water_collision() - - print("Water environment initialized") - -func setup_underwater_effects(): - # Find all cameras in the scene - var cameras = find_cameras(self) - - for camera in cameras: - # Apply underwater post-processing effect - var environment = camera.get_environment() - if not environment: - environment = Environment.new() - camera.set_environment(environment) - - # Set underwater fog - environment.fog_enabled = true - environment.fog_color = water_color - environment.fog_depth_begin = 1.0 - environment.fog_depth_end = 15.0 * water_clarity - - # Underwater light absorption (color correction) - environment.adjustment_enabled = true - environment.adjustment_brightness = 0.9 - environment.adjustment_contrast = 1.1 - environment.adjustment_saturation = 1.2 - - # Add slight color shifting toward blue/green - var color_correction = environment.get_adjustment_color_correction() - if not color_correction: - # Create a default blue-green tinted correction - var gradient = Gradient.new() - gradient.add_point(0.0, Color(0.8, 0.9, 1.0)) - gradient.add_point(1.0, Color(1.0, 1.0, 1.0)) - - var gradient_texture = GradientTexture.new() - gradient_texture.gradient = gradient - - environment.adjustment_color_correction = gradient_texture - -func find_cameras(node): - var cameras = [] - - if node is Camera: - cameras.append(node) - - for child in node.get_children(): - cameras += find_cameras(child) - - return cameras - -func add_water_collision(): - # Add area node for water effects - if water_volume: - var water_area = Area.new() - water_area.name = "WaterArea" - water_volume.add_child(water_area) - - var collision_shape = CollisionShape.new() - collision_shape.shape = water_volume.mesh.create_convex_shape() - water_area.add_child(collision_shape) - - # Connect signals - water_area.connect("body_entered", self, "_on_body_entered_water") - water_area.connect("body_exited", self, "_on_body_exited_water") - -func _on_body_entered_water(body): - # Apply water effects to objects entering water - if body.has_method("enter_water"): - body.enter_water() - - # Notify via print for debugging - print("Object entered water: " + body.name) - -func _on_body_exited_water(body): - # Remove water effects from objects exiting water - if body.has_method("exit_water"): - body.exit_water() - - # Notify via print for debugging - print("Object exited water: " + body.name) - -func add_object(object_scene, position, rotation=Vector3(0,0,0), scale=Vector3(1,1,1)): - # Instance the object - var object_instance = object_scene.instance() - add_child(object_instance) - - # Set transform - object_instance.translation = position - object_instance.rotation = rotation - object_instance.scale = scale - - # Add to objects list - objects.append(object_instance) - - return object_instance - -func add_target(target_scene, position, rotation=Vector3(0,0,0)): - # Instance the target - var target_instance = target_scene.instance() - add_child(target_instance) - - # Set transform - target_instance.translation = position - target_instance.rotation = rotation - - # Add to targets list - targets.append(target_instance) - - return target_instance - -func get_water_level(): - # Return the current water level (y-coordinate) - return water_surface.global_transform.origin.y - -func get_closest_target(position): - if targets.empty(): - return null - - var closest = targets[0] - var closest_distance = position.distance_to(closest.global_transform.origin) - - for target in targets: - var distance = position.distance_to(target.global_transform.origin) - if distance < closest_distance: - closest = target - closest_distance = distance - - return closest diff --git a/src/godot_sim/scripts/water_environment.gd.uid b/src/godot_sim/scripts/water_environment.gd.uid deleted file mode 100644 index c8615eb..0000000 --- a/src/godot_sim/scripts/water_environment.gd.uid +++ /dev/null @@ -1 +0,0 @@ -uid://dhyc08abou1mv diff --git a/tools/hydrus_cli/.python-version b/tools/hydrus_cli/.python-version new file mode 100644 index 0000000..24ee5b1 --- /dev/null +++ b/tools/hydrus_cli/.python-version @@ -0,0 +1 @@ +3.13 diff --git a/tools/hydrus_cli/README.md b/tools/hydrus_cli/README.md new file mode 100644 index 0000000..e69de29 diff --git a/scripts/commands/__init__.py b/tools/hydrus_cli/__init__.py similarity index 100% rename from scripts/commands/__init__.py rename to tools/hydrus_cli/__init__.py diff --git a/scripts/commands/arduino.py b/tools/hydrus_cli/arduino.py similarity index 95% rename from scripts/commands/arduino.py rename to tools/hydrus_cli/arduino.py index 7302874..380b41b 100644 --- a/scripts/commands/arduino.py +++ b/tools/hydrus_cli/arduino.py @@ -1,12 +1,24 @@ import json import subprocess +import sys import time from pathlib import Path import typer -from devices.Arduino.arduino import Arduino -from devices.Arduino.virtual_arduino import check_and_create_virtual_arduino +# Add the project root to the Python path +project_root = Path(__file__).parent.parent.parent +if str(project_root) not in sys.path: + sys.path.insert(0, str(project_root)) + +try: + from devices.Arduino.arduino import Arduino + from devices.Arduino.virtual_arduino import check_and_create_virtual_arduino +except ImportError as e: + typer.echo(f"Error importing Arduino modules: {e}") + typer.echo("Please ensure you are running from the project root directory.") + typer.echo(f"Expected project root: {project_root}") + raise typer.Exit(1) arduino_command = typer.Typer() diff --git a/scripts/commands/autonomy.py b/tools/hydrus_cli/autonomy.py similarity index 100% rename from scripts/commands/autonomy.py rename to tools/hydrus_cli/autonomy.py diff --git a/scripts/commands/build.py b/tools/hydrus_cli/build.py similarity index 100% rename from scripts/commands/build.py rename to tools/hydrus_cli/build.py diff --git a/scripts/cli.py b/tools/hydrus_cli/cli.py similarity index 69% rename from scripts/cli.py rename to tools/hydrus_cli/cli.py index 0b1fc61..627f6f2 100755 --- a/scripts/cli.py +++ b/tools/hydrus_cli/cli.py @@ -1,12 +1,11 @@ #!/usr/bin/env python3 import typer -from .commands.arduino import arduino_command -from .commands.autonomy import autonomy_app -from .commands.ros import ros_app -from .commands.test import test_app -from .commands.tmux import tmux_command -from .commands.todo import todo_app +from arduino import arduino_command +from autonomy import autonomy_app +from test import test_app +from tmux import tmux_command +from todo import todo_app app = typer.Typer() @@ -17,7 +16,8 @@ app.add_typer( autonomy_app, name="autonomy", help="Autonomy system and CV pipeline management" ) -app.add_typer(ros_app, name="ros", help="ROS workspace and utilities management") +# Note: ROS app is commented out until implemented +# app.add_typer(ros_app, name="ros", help="ROS workspace and utilities management") app.add_typer(test_app, name="test", help="Test suite management and execution") app.add_typer(tmux_command, name="tmux", help="Tmux session management commands") app.add_typer(todo_app, name="todo", help="TODO and FIXME tracker for code analysis") diff --git a/tools/hydrus_cli/main.py b/tools/hydrus_cli/main.py new file mode 100644 index 0000000..5e2a393 --- /dev/null +++ b/tools/hydrus_cli/main.py @@ -0,0 +1,6 @@ +def main(): + print("Hello from hydrus-cli!") + + +if __name__ == "__main__": + main() diff --git a/tools/hydrus_cli/pyproject.toml b/tools/hydrus_cli/pyproject.toml new file mode 100644 index 0000000..ddd0a7d --- /dev/null +++ b/tools/hydrus_cli/pyproject.toml @@ -0,0 +1,9 @@ +[project] +name = "hydrus-cli" +version = "0.1.0" +description = "Add your description here" +readme = "README.md" +requires-python = ">=3.12" +dependencies = [ + "typer>=0.16.1", +] diff --git a/scripts/commands/test/test.py b/tools/hydrus_cli/test.py similarity index 100% rename from scripts/commands/test/test.py rename to tools/hydrus_cli/test.py diff --git a/scripts/commands/tmux.py b/tools/hydrus_cli/tmux.py similarity index 100% rename from scripts/commands/tmux.py rename to tools/hydrus_cli/tmux.py diff --git a/scripts/commands/todo.py b/tools/hydrus_cli/todo.py similarity index 100% rename from scripts/commands/todo.py rename to tools/hydrus_cli/todo.py diff --git a/scripts/commands/utils.py b/tools/hydrus_cli/utils.py similarity index 100% rename from scripts/commands/utils.py rename to tools/hydrus_cli/utils.py diff --git a/tools/test_lib/README.md b/tools/test_lib/README.md new file mode 100644 index 0000000..3b98a58 --- /dev/null +++ b/tools/test_lib/README.md @@ -0,0 +1 @@ +This is the library for running and configuring tests in the hydrus-software-stack monorepo. diff --git a/scripts/commands/test/test_cases.py b/tools/test_lib/test_cases.py similarity index 100% rename from scripts/commands/test/test_cases.py rename to tools/test_lib/test_cases.py diff --git a/scripts/commands/test/test_config_loader.py b/tools/test_lib/test_config_loader.py similarity index 100% rename from scripts/commands/test/test_config_loader.py rename to tools/test_lib/test_config_loader.py diff --git a/scripts/commands/test/test_logger.py b/tools/test_lib/test_logger.py similarity index 100% rename from scripts/commands/test/test_logger.py rename to tools/test_lib/test_logger.py diff --git a/scripts/commands/test/test_manager.py b/tools/test_lib/test_manager.py similarity index 100% rename from scripts/commands/test/test_manager.py rename to tools/test_lib/test_manager.py diff --git a/uv.lock b/uv.lock new file mode 100644 index 0000000..d2255de --- /dev/null +++ b/uv.lock @@ -0,0 +1,1368 @@ +version = 1 +revision = 3 +requires-python = ">=3.12" +resolution-markers = [ + "sys_platform == 'darwin'", + "platform_machine == 'aarch64' and sys_platform == 'linux'", + "sys_platform == 'win32'", + "(platform_machine != 'aarch64' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != 'linux' and sys_platform != 'win32')", +] + +[manifest] +members = [ + "autonomy", + "computer-vision", + "hydrus-cli", +] + +[[package]] +name = "annotated-types" +version = "0.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081, upload-time = "2024-05-20T21:33:25.928Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643, upload-time = "2024-05-20T21:33:24.1Z" }, +] + +[[package]] +name = "anyio" +version = "4.10.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "idna" }, + { name = "sniffio" }, + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f1/b4/636b3b65173d3ce9a38ef5f0522789614e590dab6a8d505340a4efe4c567/anyio-4.10.0.tar.gz", hash = "sha256:3f3fae35c96039744587aa5b8371e7e8e603c0702999535961dd336026973ba6", size = 213252, upload-time = "2025-08-04T08:54:26.451Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6f/12/e5e0282d673bb9746bacfb6e2dba8719989d3660cdb2ea79aee9a9651afb/anyio-4.10.0-py3-none-any.whl", hash = "sha256:60e474ac86736bbfd6f210f7a61218939c318f43f9972497381f1c5e930ed3d1", size = 107213, upload-time = "2025-08-04T08:54:24.882Z" }, +] + +[[package]] +name = "autonomy" +version = "0.1.0" +source = { virtual = "autonomy" } +dependencies = [ + { name = "colorama" }, + { name = "fastapi" }, + { name = "matplotlib" }, + { name = "opencv-python" }, + { name = "pyserial" }, + { name = "requests" }, + { name = "termcolor" }, + { name = "uvicorn" }, +] + +[package.metadata] +requires-dist = [ + { name = "colorama", specifier = ">=0.4.6" }, + { name = "fastapi", specifier = ">=0.116.1" }, + { name = "matplotlib", specifier = ">=3.10.5" }, + { name = "opencv-python", specifier = ">=4.11.0.86" }, + { name = "pyserial", specifier = ">=3.5" }, + { name = "requests", specifier = ">=2.32.5" }, + { name = "termcolor", specifier = ">=3.1.0" }, + { name = "uvicorn", specifier = ">=0.35.0" }, +] + +[[package]] +name = "certifi" +version = "2025.8.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/dc/67/960ebe6bf230a96cda2e0abcf73af550ec4f090005363542f0765df162e0/certifi-2025.8.3.tar.gz", hash = "sha256:e564105f78ded564e3ae7c923924435e1daa7463faeab5bb932bc53ffae63407", size = 162386, upload-time = "2025-08-03T03:07:47.08Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e5/48/1549795ba7742c948d2ad169c1c8cdbae65bc450d6cd753d124b17c8cd32/certifi-2025.8.3-py3-none-any.whl", hash = "sha256:f6c12493cfb1b06ba2ff328595af9350c65d6644968e5d3a2ffd78699af217a5", size = 161216, upload-time = "2025-08-03T03:07:45.777Z" }, +] + +[[package]] +name = "charset-normalizer" +version = "3.4.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/83/2d/5fd176ceb9b2fc619e63405525573493ca23441330fcdaee6bef9460e924/charset_normalizer-3.4.3.tar.gz", hash = "sha256:6fce4b8500244f6fcb71465d4a4930d132ba9ab8e71a7859e6a5d59851068d14", size = 122371, upload-time = "2025-08-09T07:57:28.46Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e9/5e/14c94999e418d9b87682734589404a25854d5f5d0408df68bc15b6ff54bb/charset_normalizer-3.4.3-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:e28e334d3ff134e88989d90ba04b47d84382a828c061d0d1027b1b12a62b39b1", size = 205655, upload-time = "2025-08-09T07:56:08.475Z" }, + { url = "https://files.pythonhosted.org/packages/7d/a8/c6ec5d389672521f644505a257f50544c074cf5fc292d5390331cd6fc9c3/charset_normalizer-3.4.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0cacf8f7297b0c4fcb74227692ca46b4a5852f8f4f24b3c766dd94a1075c4884", size = 146223, upload-time = "2025-08-09T07:56:09.708Z" }, + { url = "https://files.pythonhosted.org/packages/fc/eb/a2ffb08547f4e1e5415fb69eb7db25932c52a52bed371429648db4d84fb1/charset_normalizer-3.4.3-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c6fd51128a41297f5409deab284fecbe5305ebd7e5a1f959bee1c054622b7018", size = 159366, upload-time = "2025-08-09T07:56:11.326Z" }, + { url = "https://files.pythonhosted.org/packages/82/10/0fd19f20c624b278dddaf83b8464dcddc2456cb4b02bb902a6da126b87a1/charset_normalizer-3.4.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3cfb2aad70f2c6debfbcb717f23b7eb55febc0bb23dcffc0f076009da10c6392", size = 157104, upload-time = "2025-08-09T07:56:13.014Z" }, + { url = "https://files.pythonhosted.org/packages/16/ab/0233c3231af734f5dfcf0844aa9582d5a1466c985bbed6cedab85af9bfe3/charset_normalizer-3.4.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1606f4a55c0fd363d754049cdf400175ee96c992b1f8018b993941f221221c5f", size = 151830, upload-time = "2025-08-09T07:56:14.428Z" }, + { url = "https://files.pythonhosted.org/packages/ae/02/e29e22b4e02839a0e4a06557b1999d0a47db3567e82989b5bb21f3fbbd9f/charset_normalizer-3.4.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:027b776c26d38b7f15b26a5da1044f376455fb3766df8fc38563b4efbc515154", size = 148854, upload-time = "2025-08-09T07:56:16.051Z" }, + { url = "https://files.pythonhosted.org/packages/05/6b/e2539a0a4be302b481e8cafb5af8792da8093b486885a1ae4d15d452bcec/charset_normalizer-3.4.3-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:42e5088973e56e31e4fa58eb6bd709e42fc03799c11c42929592889a2e54c491", size = 160670, upload-time = "2025-08-09T07:56:17.314Z" }, + { url = "https://files.pythonhosted.org/packages/31/e7/883ee5676a2ef217a40ce0bffcc3d0dfbf9e64cbcfbdf822c52981c3304b/charset_normalizer-3.4.3-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:cc34f233c9e71701040d772aa7490318673aa7164a0efe3172b2981218c26d93", size = 158501, upload-time = "2025-08-09T07:56:18.641Z" }, + { url = "https://files.pythonhosted.org/packages/c1/35/6525b21aa0db614cf8b5792d232021dca3df7f90a1944db934efa5d20bb1/charset_normalizer-3.4.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:320e8e66157cc4e247d9ddca8e21f427efc7a04bbd0ac8a9faf56583fa543f9f", size = 153173, upload-time = "2025-08-09T07:56:20.289Z" }, + { url = "https://files.pythonhosted.org/packages/50/ee/f4704bad8201de513fdc8aac1cabc87e38c5818c93857140e06e772b5892/charset_normalizer-3.4.3-cp312-cp312-win32.whl", hash = "sha256:fb6fecfd65564f208cbf0fba07f107fb661bcd1a7c389edbced3f7a493f70e37", size = 99822, upload-time = "2025-08-09T07:56:21.551Z" }, + { url = "https://files.pythonhosted.org/packages/39/f5/3b3836ca6064d0992c58c7561c6b6eee1b3892e9665d650c803bd5614522/charset_normalizer-3.4.3-cp312-cp312-win_amd64.whl", hash = "sha256:86df271bf921c2ee3818f0522e9a5b8092ca2ad8b065ece5d7d9d0e9f4849bcc", size = 107543, upload-time = "2025-08-09T07:56:23.115Z" }, + { url = "https://files.pythonhosted.org/packages/65/ca/2135ac97709b400c7654b4b764daf5c5567c2da45a30cdd20f9eefe2d658/charset_normalizer-3.4.3-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:14c2a87c65b351109f6abfc424cab3927b3bdece6f706e4d12faaf3d52ee5efe", size = 205326, upload-time = "2025-08-09T07:56:24.721Z" }, + { url = "https://files.pythonhosted.org/packages/71/11/98a04c3c97dd34e49c7d247083af03645ca3730809a5509443f3c37f7c99/charset_normalizer-3.4.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:41d1fc408ff5fdfb910200ec0e74abc40387bccb3252f3f27c0676731df2b2c8", size = 146008, upload-time = "2025-08-09T07:56:26.004Z" }, + { url = "https://files.pythonhosted.org/packages/60/f5/4659a4cb3c4ec146bec80c32d8bb16033752574c20b1252ee842a95d1a1e/charset_normalizer-3.4.3-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:1bb60174149316da1c35fa5233681f7c0f9f514509b8e399ab70fea5f17e45c9", size = 159196, upload-time = "2025-08-09T07:56:27.25Z" }, + { url = "https://files.pythonhosted.org/packages/86/9e/f552f7a00611f168b9a5865a1414179b2c6de8235a4fa40189f6f79a1753/charset_normalizer-3.4.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:30d006f98569de3459c2fc1f2acde170b7b2bd265dc1943e87e1a4efe1b67c31", size = 156819, upload-time = "2025-08-09T07:56:28.515Z" }, + { url = "https://files.pythonhosted.org/packages/7e/95/42aa2156235cbc8fa61208aded06ef46111c4d3f0de233107b3f38631803/charset_normalizer-3.4.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:416175faf02e4b0810f1f38bcb54682878a4af94059a1cd63b8747244420801f", size = 151350, upload-time = "2025-08-09T07:56:29.716Z" }, + { url = "https://files.pythonhosted.org/packages/c2/a9/3865b02c56f300a6f94fc631ef54f0a8a29da74fb45a773dfd3dcd380af7/charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6aab0f181c486f973bc7262a97f5aca3ee7e1437011ef0c2ec04b5a11d16c927", size = 148644, upload-time = "2025-08-09T07:56:30.984Z" }, + { url = "https://files.pythonhosted.org/packages/77/d9/cbcf1a2a5c7d7856f11e7ac2d782aec12bdfea60d104e60e0aa1c97849dc/charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:fdabf8315679312cfa71302f9bd509ded4f2f263fb5b765cf1433b39106c3cc9", size = 160468, upload-time = "2025-08-09T07:56:32.252Z" }, + { url = "https://files.pythonhosted.org/packages/f6/42/6f45efee8697b89fda4d50580f292b8f7f9306cb2971d4b53f8914e4d890/charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:bd28b817ea8c70215401f657edef3a8aa83c29d447fb0b622c35403780ba11d5", size = 158187, upload-time = "2025-08-09T07:56:33.481Z" }, + { url = "https://files.pythonhosted.org/packages/70/99/f1c3bdcfaa9c45b3ce96f70b14f070411366fa19549c1d4832c935d8e2c3/charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:18343b2d246dc6761a249ba1fb13f9ee9a2bcd95decc767319506056ea4ad4dc", size = 152699, upload-time = "2025-08-09T07:56:34.739Z" }, + { url = "https://files.pythonhosted.org/packages/a3/ad/b0081f2f99a4b194bcbb1934ef3b12aa4d9702ced80a37026b7607c72e58/charset_normalizer-3.4.3-cp313-cp313-win32.whl", hash = "sha256:6fb70de56f1859a3f71261cbe41005f56a7842cc348d3aeb26237560bfa5e0ce", size = 99580, upload-time = "2025-08-09T07:56:35.981Z" }, + { url = "https://files.pythonhosted.org/packages/9a/8f/ae790790c7b64f925e5c953b924aaa42a243fb778fed9e41f147b2a5715a/charset_normalizer-3.4.3-cp313-cp313-win_amd64.whl", hash = "sha256:cf1ebb7d78e1ad8ec2a8c4732c7be2e736f6e5123a4146c5b89c9d1f585f8cef", size = 107366, upload-time = "2025-08-09T07:56:37.339Z" }, + { url = "https://files.pythonhosted.org/packages/8e/91/b5a06ad970ddc7a0e513112d40113e834638f4ca1120eb727a249fb2715e/charset_normalizer-3.4.3-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:3cd35b7e8aedeb9e34c41385fda4f73ba609e561faedfae0a9e75e44ac558a15", size = 204342, upload-time = "2025-08-09T07:56:38.687Z" }, + { url = "https://files.pythonhosted.org/packages/ce/ec/1edc30a377f0a02689342f214455c3f6c2fbedd896a1d2f856c002fc3062/charset_normalizer-3.4.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b89bc04de1d83006373429975f8ef9e7932534b8cc9ca582e4db7d20d91816db", size = 145995, upload-time = "2025-08-09T07:56:40.048Z" }, + { url = "https://files.pythonhosted.org/packages/17/e5/5e67ab85e6d22b04641acb5399c8684f4d37caf7558a53859f0283a650e9/charset_normalizer-3.4.3-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2001a39612b241dae17b4687898843f254f8748b796a2e16f1051a17078d991d", size = 158640, upload-time = "2025-08-09T07:56:41.311Z" }, + { url = "https://files.pythonhosted.org/packages/f1/e5/38421987f6c697ee3722981289d554957c4be652f963d71c5e46a262e135/charset_normalizer-3.4.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8dcfc373f888e4fb39a7bc57e93e3b845e7f462dacc008d9749568b1c4ece096", size = 156636, upload-time = "2025-08-09T07:56:43.195Z" }, + { url = "https://files.pythonhosted.org/packages/a0/e4/5a075de8daa3ec0745a9a3b54467e0c2967daaaf2cec04c845f73493e9a1/charset_normalizer-3.4.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:18b97b8404387b96cdbd30ad660f6407799126d26a39ca65729162fd810a99aa", size = 150939, upload-time = "2025-08-09T07:56:44.819Z" }, + { url = "https://files.pythonhosted.org/packages/02/f7/3611b32318b30974131db62b4043f335861d4d9b49adc6d57c1149cc49d4/charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ccf600859c183d70eb47e05a44cd80a4ce77394d1ac0f79dbd2dd90a69a3a049", size = 148580, upload-time = "2025-08-09T07:56:46.684Z" }, + { url = "https://files.pythonhosted.org/packages/7e/61/19b36f4bd67f2793ab6a99b979b4e4f3d8fc754cbdffb805335df4337126/charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:53cd68b185d98dde4ad8990e56a58dea83a4162161b1ea9272e5c9182ce415e0", size = 159870, upload-time = "2025-08-09T07:56:47.941Z" }, + { url = "https://files.pythonhosted.org/packages/06/57/84722eefdd338c04cf3030ada66889298eaedf3e7a30a624201e0cbe424a/charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:30a96e1e1f865f78b030d65241c1ee850cdf422d869e9028e2fc1d5e4db73b92", size = 157797, upload-time = "2025-08-09T07:56:49.756Z" }, + { url = "https://files.pythonhosted.org/packages/72/2a/aff5dd112b2f14bcc3462c312dce5445806bfc8ab3a7328555da95330e4b/charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:d716a916938e03231e86e43782ca7878fb602a125a91e7acb8b5112e2e96ac16", size = 152224, upload-time = "2025-08-09T07:56:51.369Z" }, + { url = "https://files.pythonhosted.org/packages/b7/8c/9839225320046ed279c6e839d51f028342eb77c91c89b8ef2549f951f3ec/charset_normalizer-3.4.3-cp314-cp314-win32.whl", hash = "sha256:c6dbd0ccdda3a2ba7c2ecd9d77b37f3b5831687d8dc1b6ca5f56a4880cc7b7ce", size = 100086, upload-time = "2025-08-09T07:56:52.722Z" }, + { url = "https://files.pythonhosted.org/packages/ee/7a/36fbcf646e41f710ce0a563c1c9a343c6edf9be80786edeb15b6f62e17db/charset_normalizer-3.4.3-cp314-cp314-win_amd64.whl", hash = "sha256:73dc19b562516fc9bcf6e5d6e596df0b4eb98d87e4f79f3ae71840e6ed21361c", size = 107400, upload-time = "2025-08-09T07:56:55.172Z" }, + { url = "https://files.pythonhosted.org/packages/8a/1f/f041989e93b001bc4e44bb1669ccdcf54d3f00e628229a85b08d330615c5/charset_normalizer-3.4.3-py3-none-any.whl", hash = "sha256:ce571ab16d890d23b5c278547ba694193a45011ff86a9162a71307ed9f86759a", size = 53175, upload-time = "2025-08-09T07:57:26.864Z" }, +] + +[[package]] +name = "click" +version = "8.2.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/60/6c/8ca2efa64cf75a977a0d7fac081354553ebe483345c734fb6b6515d96bbc/click-8.2.1.tar.gz", hash = "sha256:27c491cc05d968d271d5a1db13e3b5a184636d9d930f148c50b038f0d0646202", size = 286342, upload-time = "2025-05-20T23:19:49.832Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/85/32/10bb5764d90a8eee674e9dc6f4db6a0ab47c8c4d0d83c27f7c39ac415a4d/click-8.2.1-py3-none-any.whl", hash = "sha256:61a3265b914e850b85317d0b3109c7f8cd35a670f963866005d6ef1d5175a12b", size = 102215, upload-time = "2025-05-20T23:19:47.796Z" }, +] + +[[package]] +name = "colorama" +version = "0.4.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, +] + +[[package]] +name = "computer-vision" +version = "0.1.0" +source = { editable = "src/computer_vision" } +dependencies = [ + { name = "numpy" }, + { name = "opencv-python" }, + { name = "ultralytics" }, +] + +[package.metadata] +requires-dist = [ + { name = "numpy", specifier = ">=2.3.2" }, + { name = "opencv-python", specifier = ">=4.11.0.86" }, + { name = "ultralytics", specifier = ">=8.3.182" }, +] + +[[package]] +name = "contourpy" +version = "1.3.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/58/01/1253e6698a07380cd31a736d248a3f2a50a7c88779a1813da27503cadc2a/contourpy-1.3.3.tar.gz", hash = "sha256:083e12155b210502d0bca491432bb04d56dc3432f95a979b429f2848c3dbe880", size = 13466174, upload-time = "2025-07-26T12:03:12.549Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/be/45/adfee365d9ea3d853550b2e735f9d66366701c65db7855cd07621732ccfc/contourpy-1.3.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b08a32ea2f8e42cf1d4be3169a98dd4be32bafe4f22b6c4cb4ba810fa9e5d2cb", size = 293419, upload-time = "2025-07-26T12:01:21.16Z" }, + { url = "https://files.pythonhosted.org/packages/53/3e/405b59cfa13021a56bba395a6b3aca8cec012b45bf177b0eaf7a202cde2c/contourpy-1.3.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:556dba8fb6f5d8742f2923fe9457dbdd51e1049c4a43fd3986a0b14a1d815fc6", size = 273979, upload-time = "2025-07-26T12:01:22.448Z" }, + { url = "https://files.pythonhosted.org/packages/d4/1c/a12359b9b2ca3a845e8f7f9ac08bdf776114eb931392fcad91743e2ea17b/contourpy-1.3.3-cp312-cp312-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:92d9abc807cf7d0e047b95ca5d957cf4792fcd04e920ca70d48add15c1a90ea7", size = 332653, upload-time = "2025-07-26T12:01:24.155Z" }, + { url = "https://files.pythonhosted.org/packages/63/12/897aeebfb475b7748ea67b61e045accdfcf0d971f8a588b67108ed7f5512/contourpy-1.3.3-cp312-cp312-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b2e8faa0ed68cb29af51edd8e24798bb661eac3bd9f65420c1887b6ca89987c8", size = 379536, upload-time = "2025-07-26T12:01:25.91Z" }, + { url = "https://files.pythonhosted.org/packages/43/8a/a8c584b82deb248930ce069e71576fc09bd7174bbd35183b7943fb1064fd/contourpy-1.3.3-cp312-cp312-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:626d60935cf668e70a5ce6ff184fd713e9683fb458898e4249b63be9e28286ea", size = 384397, upload-time = "2025-07-26T12:01:27.152Z" }, + { url = "https://files.pythonhosted.org/packages/cc/8f/ec6289987824b29529d0dfda0d74a07cec60e54b9c92f3c9da4c0ac732de/contourpy-1.3.3-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4d00e655fcef08aba35ec9610536bfe90267d7ab5ba944f7032549c55a146da1", size = 362601, upload-time = "2025-07-26T12:01:28.808Z" }, + { url = "https://files.pythonhosted.org/packages/05/0a/a3fe3be3ee2dceb3e615ebb4df97ae6f3828aa915d3e10549ce016302bd1/contourpy-1.3.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:451e71b5a7d597379ef572de31eeb909a87246974d960049a9848c3bc6c41bf7", size = 1331288, upload-time = "2025-07-26T12:01:31.198Z" }, + { url = "https://files.pythonhosted.org/packages/33/1d/acad9bd4e97f13f3e2b18a3977fe1b4a37ecf3d38d815333980c6c72e963/contourpy-1.3.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:459c1f020cd59fcfe6650180678a9993932d80d44ccde1fa1868977438f0b411", size = 1403386, upload-time = "2025-07-26T12:01:33.947Z" }, + { url = "https://files.pythonhosted.org/packages/cf/8f/5847f44a7fddf859704217a99a23a4f6417b10e5ab1256a179264561540e/contourpy-1.3.3-cp312-cp312-win32.whl", hash = "sha256:023b44101dfe49d7d53932be418477dba359649246075c996866106da069af69", size = 185018, upload-time = "2025-07-26T12:01:35.64Z" }, + { url = "https://files.pythonhosted.org/packages/19/e8/6026ed58a64563186a9ee3f29f41261fd1828f527dd93d33b60feca63352/contourpy-1.3.3-cp312-cp312-win_amd64.whl", hash = "sha256:8153b8bfc11e1e4d75bcb0bff1db232f9e10b274e0929de9d608027e0d34ff8b", size = 226567, upload-time = "2025-07-26T12:01:36.804Z" }, + { url = "https://files.pythonhosted.org/packages/d1/e2/f05240d2c39a1ed228d8328a78b6f44cd695f7ef47beb3e684cf93604f86/contourpy-1.3.3-cp312-cp312-win_arm64.whl", hash = "sha256:07ce5ed73ecdc4a03ffe3e1b3e3c1166db35ae7584be76f65dbbe28a7791b0cc", size = 193655, upload-time = "2025-07-26T12:01:37.999Z" }, + { url = "https://files.pythonhosted.org/packages/68/35/0167aad910bbdb9599272bd96d01a9ec6852f36b9455cf2ca67bd4cc2d23/contourpy-1.3.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:177fb367556747a686509d6fef71d221a4b198a3905fe824430e5ea0fda54eb5", size = 293257, upload-time = "2025-07-26T12:01:39.367Z" }, + { url = "https://files.pythonhosted.org/packages/96/e4/7adcd9c8362745b2210728f209bfbcf7d91ba868a2c5f40d8b58f54c509b/contourpy-1.3.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d002b6f00d73d69333dac9d0b8d5e84d9724ff9ef044fd63c5986e62b7c9e1b1", size = 274034, upload-time = "2025-07-26T12:01:40.645Z" }, + { url = "https://files.pythonhosted.org/packages/73/23/90e31ceeed1de63058a02cb04b12f2de4b40e3bef5e082a7c18d9c8ae281/contourpy-1.3.3-cp313-cp313-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:348ac1f5d4f1d66d3322420f01d42e43122f43616e0f194fc1c9f5d830c5b286", size = 334672, upload-time = "2025-07-26T12:01:41.942Z" }, + { url = "https://files.pythonhosted.org/packages/ed/93/b43d8acbe67392e659e1d984700e79eb67e2acb2bd7f62012b583a7f1b55/contourpy-1.3.3-cp313-cp313-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:655456777ff65c2c548b7c454af9c6f33f16c8884f11083244b5819cc214f1b5", size = 381234, upload-time = "2025-07-26T12:01:43.499Z" }, + { url = "https://files.pythonhosted.org/packages/46/3b/bec82a3ea06f66711520f75a40c8fc0b113b2a75edb36aa633eb11c4f50f/contourpy-1.3.3-cp313-cp313-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:644a6853d15b2512d67881586bd03f462c7ab755db95f16f14d7e238f2852c67", size = 385169, upload-time = "2025-07-26T12:01:45.219Z" }, + { url = "https://files.pythonhosted.org/packages/4b/32/e0f13a1c5b0f8572d0ec6ae2f6c677b7991fafd95da523159c19eff0696a/contourpy-1.3.3-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4debd64f124ca62069f313a9cb86656ff087786016d76927ae2cf37846b006c9", size = 362859, upload-time = "2025-07-26T12:01:46.519Z" }, + { url = "https://files.pythonhosted.org/packages/33/71/e2a7945b7de4e58af42d708a219f3b2f4cff7386e6b6ab0a0fa0033c49a9/contourpy-1.3.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a15459b0f4615b00bbd1e91f1b9e19b7e63aea7483d03d804186f278c0af2659", size = 1332062, upload-time = "2025-07-26T12:01:48.964Z" }, + { url = "https://files.pythonhosted.org/packages/12/fc/4e87ac754220ccc0e807284f88e943d6d43b43843614f0a8afa469801db0/contourpy-1.3.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ca0fdcd73925568ca027e0b17ab07aad764be4706d0a925b89227e447d9737b7", size = 1403932, upload-time = "2025-07-26T12:01:51.979Z" }, + { url = "https://files.pythonhosted.org/packages/a6/2e/adc197a37443f934594112222ac1aa7dc9a98faf9c3842884df9a9d8751d/contourpy-1.3.3-cp313-cp313-win32.whl", hash = "sha256:b20c7c9a3bf701366556e1b1984ed2d0cedf999903c51311417cf5f591d8c78d", size = 185024, upload-time = "2025-07-26T12:01:53.245Z" }, + { url = "https://files.pythonhosted.org/packages/18/0b/0098c214843213759692cc638fce7de5c289200a830e5035d1791d7a2338/contourpy-1.3.3-cp313-cp313-win_amd64.whl", hash = "sha256:1cadd8b8969f060ba45ed7c1b714fe69185812ab43bd6b86a9123fe8f99c3263", size = 226578, upload-time = "2025-07-26T12:01:54.422Z" }, + { url = "https://files.pythonhosted.org/packages/8a/9a/2f6024a0c5995243cd63afdeb3651c984f0d2bc727fd98066d40e141ad73/contourpy-1.3.3-cp313-cp313-win_arm64.whl", hash = "sha256:fd914713266421b7536de2bfa8181aa8c699432b6763a0ea64195ebe28bff6a9", size = 193524, upload-time = "2025-07-26T12:01:55.73Z" }, + { url = "https://files.pythonhosted.org/packages/c0/b3/f8a1a86bd3298513f500e5b1f5fd92b69896449f6cab6a146a5d52715479/contourpy-1.3.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:88df9880d507169449d434c293467418b9f6cbe82edd19284aa0409e7fdb933d", size = 306730, upload-time = "2025-07-26T12:01:57.051Z" }, + { url = "https://files.pythonhosted.org/packages/3f/11/4780db94ae62fc0c2053909b65dc3246bd7cecfc4f8a20d957ad43aa4ad8/contourpy-1.3.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:d06bb1f751ba5d417047db62bca3c8fde202b8c11fb50742ab3ab962c81e8216", size = 287897, upload-time = "2025-07-26T12:01:58.663Z" }, + { url = "https://files.pythonhosted.org/packages/ae/15/e59f5f3ffdd6f3d4daa3e47114c53daabcb18574a26c21f03dc9e4e42ff0/contourpy-1.3.3-cp313-cp313t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e4e6b05a45525357e382909a4c1600444e2a45b4795163d3b22669285591c1ae", size = 326751, upload-time = "2025-07-26T12:02:00.343Z" }, + { url = "https://files.pythonhosted.org/packages/0f/81/03b45cfad088e4770b1dcf72ea78d3802d04200009fb364d18a493857210/contourpy-1.3.3-cp313-cp313t-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ab3074b48c4e2cf1a960e6bbeb7f04566bf36b1861d5c9d4d8ac04b82e38ba20", size = 375486, upload-time = "2025-07-26T12:02:02.128Z" }, + { url = "https://files.pythonhosted.org/packages/0c/ba/49923366492ffbdd4486e970d421b289a670ae8cf539c1ea9a09822b371a/contourpy-1.3.3-cp313-cp313t-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:6c3d53c796f8647d6deb1abe867daeb66dcc8a97e8455efa729516b997b8ed99", size = 388106, upload-time = "2025-07-26T12:02:03.615Z" }, + { url = "https://files.pythonhosted.org/packages/9f/52/5b00ea89525f8f143651f9f03a0df371d3cbd2fccd21ca9b768c7a6500c2/contourpy-1.3.3-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:50ed930df7289ff2a8d7afeb9603f8289e5704755c7e5c3bbd929c90c817164b", size = 352548, upload-time = "2025-07-26T12:02:05.165Z" }, + { url = "https://files.pythonhosted.org/packages/32/1d/a209ec1a3a3452d490f6b14dd92e72280c99ae3d1e73da74f8277d4ee08f/contourpy-1.3.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:4feffb6537d64b84877da813a5c30f1422ea5739566abf0bd18065ac040e120a", size = 1322297, upload-time = "2025-07-26T12:02:07.379Z" }, + { url = "https://files.pythonhosted.org/packages/bc/9e/46f0e8ebdd884ca0e8877e46a3f4e633f6c9c8c4f3f6e72be3fe075994aa/contourpy-1.3.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:2b7e9480ffe2b0cd2e787e4df64270e3a0440d9db8dc823312e2c940c167df7e", size = 1391023, upload-time = "2025-07-26T12:02:10.171Z" }, + { url = "https://files.pythonhosted.org/packages/b9/70/f308384a3ae9cd2209e0849f33c913f658d3326900d0ff5d378d6a1422d2/contourpy-1.3.3-cp313-cp313t-win32.whl", hash = "sha256:283edd842a01e3dcd435b1c5116798d661378d83d36d337b8dde1d16a5fc9ba3", size = 196157, upload-time = "2025-07-26T12:02:11.488Z" }, + { url = "https://files.pythonhosted.org/packages/b2/dd/880f890a6663b84d9e34a6f88cded89d78f0091e0045a284427cb6b18521/contourpy-1.3.3-cp313-cp313t-win_amd64.whl", hash = "sha256:87acf5963fc2b34825e5b6b048f40e3635dd547f590b04d2ab317c2619ef7ae8", size = 240570, upload-time = "2025-07-26T12:02:12.754Z" }, + { url = "https://files.pythonhosted.org/packages/80/99/2adc7d8ffead633234817ef8e9a87115c8a11927a94478f6bb3d3f4d4f7d/contourpy-1.3.3-cp313-cp313t-win_arm64.whl", hash = "sha256:3c30273eb2a55024ff31ba7d052dde990d7d8e5450f4bbb6e913558b3d6c2301", size = 199713, upload-time = "2025-07-26T12:02:14.4Z" }, + { url = "https://files.pythonhosted.org/packages/72/8b/4546f3ab60f78c514ffb7d01a0bd743f90de36f0019d1be84d0a708a580a/contourpy-1.3.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:fde6c716d51c04b1c25d0b90364d0be954624a0ee9d60e23e850e8d48353d07a", size = 292189, upload-time = "2025-07-26T12:02:16.095Z" }, + { url = "https://files.pythonhosted.org/packages/fd/e1/3542a9cb596cadd76fcef413f19c79216e002623158befe6daa03dbfa88c/contourpy-1.3.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:cbedb772ed74ff5be440fa8eee9bd49f64f6e3fc09436d9c7d8f1c287b121d77", size = 273251, upload-time = "2025-07-26T12:02:17.524Z" }, + { url = "https://files.pythonhosted.org/packages/b1/71/f93e1e9471d189f79d0ce2497007731c1e6bf9ef6d1d61b911430c3db4e5/contourpy-1.3.3-cp314-cp314-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:22e9b1bd7a9b1d652cd77388465dc358dafcd2e217d35552424aa4f996f524f5", size = 335810, upload-time = "2025-07-26T12:02:18.9Z" }, + { url = "https://files.pythonhosted.org/packages/91/f9/e35f4c1c93f9275d4e38681a80506b5510e9327350c51f8d4a5a724d178c/contourpy-1.3.3-cp314-cp314-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a22738912262aa3e254e4f3cb079a95a67132fc5a063890e224393596902f5a4", size = 382871, upload-time = "2025-07-26T12:02:20.418Z" }, + { url = "https://files.pythonhosted.org/packages/b5/71/47b512f936f66a0a900d81c396a7e60d73419868fba959c61efed7a8ab46/contourpy-1.3.3-cp314-cp314-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:afe5a512f31ee6bd7d0dda52ec9864c984ca3d66664444f2d72e0dc4eb832e36", size = 386264, upload-time = "2025-07-26T12:02:21.916Z" }, + { url = "https://files.pythonhosted.org/packages/04/5f/9ff93450ba96b09c7c2b3f81c94de31c89f92292f1380261bd7195bea4ea/contourpy-1.3.3-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f64836de09927cba6f79dcd00fdd7d5329f3fccc633468507079c829ca4db4e3", size = 363819, upload-time = "2025-07-26T12:02:23.759Z" }, + { url = "https://files.pythonhosted.org/packages/3e/a6/0b185d4cc480ee494945cde102cb0149ae830b5fa17bf855b95f2e70ad13/contourpy-1.3.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:1fd43c3be4c8e5fd6e4f2baeae35ae18176cf2e5cced681cca908addf1cdd53b", size = 1333650, upload-time = "2025-07-26T12:02:26.181Z" }, + { url = "https://files.pythonhosted.org/packages/43/d7/afdc95580ca56f30fbcd3060250f66cedbde69b4547028863abd8aa3b47e/contourpy-1.3.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:6afc576f7b33cf00996e5c1102dc2a8f7cc89e39c0b55df93a0b78c1bd992b36", size = 1404833, upload-time = "2025-07-26T12:02:28.782Z" }, + { url = "https://files.pythonhosted.org/packages/e2/e2/366af18a6d386f41132a48f033cbd2102e9b0cf6345d35ff0826cd984566/contourpy-1.3.3-cp314-cp314-win32.whl", hash = "sha256:66c8a43a4f7b8df8b71ee1840e4211a3c8d93b214b213f590e18a1beca458f7d", size = 189692, upload-time = "2025-07-26T12:02:30.128Z" }, + { url = "https://files.pythonhosted.org/packages/7d/c2/57f54b03d0f22d4044b8afb9ca0e184f8b1afd57b4f735c2fa70883dc601/contourpy-1.3.3-cp314-cp314-win_amd64.whl", hash = "sha256:cf9022ef053f2694e31d630feaacb21ea24224be1c3ad0520b13d844274614fd", size = 232424, upload-time = "2025-07-26T12:02:31.395Z" }, + { url = "https://files.pythonhosted.org/packages/18/79/a9416650df9b525737ab521aa181ccc42d56016d2123ddcb7b58e926a42c/contourpy-1.3.3-cp314-cp314-win_arm64.whl", hash = "sha256:95b181891b4c71de4bb404c6621e7e2390745f887f2a026b2d99e92c17892339", size = 198300, upload-time = "2025-07-26T12:02:32.956Z" }, + { url = "https://files.pythonhosted.org/packages/1f/42/38c159a7d0f2b7b9c04c64ab317042bb6952b713ba875c1681529a2932fe/contourpy-1.3.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:33c82d0138c0a062380332c861387650c82e4cf1747aaa6938b9b6516762e772", size = 306769, upload-time = "2025-07-26T12:02:34.2Z" }, + { url = "https://files.pythonhosted.org/packages/c3/6c/26a8205f24bca10974e77460de68d3d7c63e282e23782f1239f226fcae6f/contourpy-1.3.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:ea37e7b45949df430fe649e5de8351c423430046a2af20b1c1961cae3afcda77", size = 287892, upload-time = "2025-07-26T12:02:35.807Z" }, + { url = "https://files.pythonhosted.org/packages/66/06/8a475c8ab718ebfd7925661747dbb3c3ee9c82ac834ccb3570be49d129f4/contourpy-1.3.3-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d304906ecc71672e9c89e87c4675dc5c2645e1f4269a5063b99b0bb29f232d13", size = 326748, upload-time = "2025-07-26T12:02:37.193Z" }, + { url = "https://files.pythonhosted.org/packages/b4/a3/c5ca9f010a44c223f098fccd8b158bb1cb287378a31ac141f04730dc49be/contourpy-1.3.3-cp314-cp314t-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ca658cd1a680a5c9ea96dc61cdbae1e85c8f25849843aa799dfd3cb370ad4fbe", size = 375554, upload-time = "2025-07-26T12:02:38.894Z" }, + { url = "https://files.pythonhosted.org/packages/80/5b/68bd33ae63fac658a4145088c1e894405e07584a316738710b636c6d0333/contourpy-1.3.3-cp314-cp314t-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ab2fd90904c503739a75b7c8c5c01160130ba67944a7b77bbf36ef8054576e7f", size = 388118, upload-time = "2025-07-26T12:02:40.642Z" }, + { url = "https://files.pythonhosted.org/packages/40/52/4c285a6435940ae25d7410a6c36bda5145839bc3f0beb20c707cda18b9d2/contourpy-1.3.3-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b7301b89040075c30e5768810bc96a8e8d78085b47d8be6e4c3f5a0b4ed478a0", size = 352555, upload-time = "2025-07-26T12:02:42.25Z" }, + { url = "https://files.pythonhosted.org/packages/24/ee/3e81e1dd174f5c7fefe50e85d0892de05ca4e26ef1c9a59c2a57e43b865a/contourpy-1.3.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:2a2a8b627d5cc6b7c41a4beff6c5ad5eb848c88255fda4a8745f7e901b32d8e4", size = 1322295, upload-time = "2025-07-26T12:02:44.668Z" }, + { url = "https://files.pythonhosted.org/packages/3c/b2/6d913d4d04e14379de429057cd169e5e00f6c2af3bb13e1710bcbdb5da12/contourpy-1.3.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:fd6ec6be509c787f1caf6b247f0b1ca598bef13f4ddeaa126b7658215529ba0f", size = 1391027, upload-time = "2025-07-26T12:02:47.09Z" }, + { url = "https://files.pythonhosted.org/packages/93/8a/68a4ec5c55a2971213d29a9374913f7e9f18581945a7a31d1a39b5d2dfe5/contourpy-1.3.3-cp314-cp314t-win32.whl", hash = "sha256:e74a9a0f5e3fff48fb5a7f2fd2b9b70a3fe014a67522f79b7cca4c0c7e43c9ae", size = 202428, upload-time = "2025-07-26T12:02:48.691Z" }, + { url = "https://files.pythonhosted.org/packages/fa/96/fd9f641ffedc4fa3ace923af73b9d07e869496c9cc7a459103e6e978992f/contourpy-1.3.3-cp314-cp314t-win_amd64.whl", hash = "sha256:13b68d6a62db8eafaebb8039218921399baf6e47bf85006fd8529f2a08ef33fc", size = 250331, upload-time = "2025-07-26T12:02:50.137Z" }, + { url = "https://files.pythonhosted.org/packages/ae/8c/469afb6465b853afff216f9528ffda78a915ff880ed58813ba4faf4ba0b6/contourpy-1.3.3-cp314-cp314t-win_arm64.whl", hash = "sha256:b7448cb5a725bb1e35ce88771b86fba35ef418952474492cf7c764059933ff8b", size = 203831, upload-time = "2025-07-26T12:02:51.449Z" }, +] + +[[package]] +name = "cycler" +version = "0.12.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a9/95/a3dbbb5028f35eafb79008e7522a75244477d2838f38cbb722248dabc2a8/cycler-0.12.1.tar.gz", hash = "sha256:88bb128f02ba341da8ef447245a9e138fae777f6a23943da4540077d3601eb1c", size = 7615, upload-time = "2023-10-07T05:32:18.335Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl", hash = "sha256:85cef7cff222d8644161529808465972e51340599459b8ac3ccbac5a854e0d30", size = 8321, upload-time = "2023-10-07T05:32:16.783Z" }, +] + +[[package]] +name = "fastapi" +version = "0.116.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pydantic" }, + { name = "starlette" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/78/d7/6c8b3bfe33eeffa208183ec037fee0cce9f7f024089ab1c5d12ef04bd27c/fastapi-0.116.1.tar.gz", hash = "sha256:ed52cbf946abfd70c5a0dccb24673f0670deeb517a88b3544d03c2a6bf283143", size = 296485, upload-time = "2025-07-11T16:22:32.057Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e5/47/d63c60f59a59467fda0f93f46335c9d18526d7071f025cb5b89d5353ea42/fastapi-0.116.1-py3-none-any.whl", hash = "sha256:c46ac7c312df840f0c9e220f7964bada936781bc4e2e6eb71f1c4d7553786565", size = 95631, upload-time = "2025-07-11T16:22:30.485Z" }, +] + +[[package]] +name = "filelock" +version = "3.19.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/40/bb/0ab3e58d22305b6f5440629d20683af28959bf793d98d11950e305c1c326/filelock-3.19.1.tar.gz", hash = "sha256:66eda1888b0171c998b35be2bcc0f6d75c388a7ce20c3f3f37aa8e96c2dddf58", size = 17687, upload-time = "2025-08-14T16:56:03.016Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/42/14/42b2651a2f46b022ccd948bca9f2d5af0fd8929c4eec235b8d6d844fbe67/filelock-3.19.1-py3-none-any.whl", hash = "sha256:d38e30481def20772f5baf097c122c3babc4fcdb7e14e57049eb9d88c6dc017d", size = 15988, upload-time = "2025-08-14T16:56:01.633Z" }, +] + +[[package]] +name = "fonttools" +version = "4.59.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/11/7f/29c9c3fe4246f6ad96fee52b88d0dc3a863c7563b0afc959e36d78b965dc/fonttools-4.59.1.tar.gz", hash = "sha256:74995b402ad09822a4c8002438e54940d9f1ecda898d2bb057729d7da983e4cb", size = 3534394, upload-time = "2025-08-14T16:28:14.266Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ac/fe/6e069cc4cb8881d164a9bd956e9df555bc62d3eb36f6282e43440200009c/fonttools-4.59.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:43ab814bbba5f02a93a152ee61a04182bb5809bd2bc3609f7822e12c53ae2c91", size = 2769172, upload-time = "2025-08-14T16:26:45.729Z" }, + { url = "https://files.pythonhosted.org/packages/b9/98/ec4e03f748fefa0dd72d9d95235aff6fef16601267f4a2340f0e16b9330f/fonttools-4.59.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4f04c3ffbfa0baafcbc550657cf83657034eb63304d27b05cff1653b448ccff6", size = 2337281, upload-time = "2025-08-14T16:26:47.921Z" }, + { url = "https://files.pythonhosted.org/packages/8b/b1/890360a7e3d04a30ba50b267aca2783f4c1364363797e892e78a4f036076/fonttools-4.59.1-cp312-cp312-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:d601b153e51a5a6221f0d4ec077b6bfc6ac35bfe6c19aeaa233d8990b2b71726", size = 4909215, upload-time = "2025-08-14T16:26:49.682Z" }, + { url = "https://files.pythonhosted.org/packages/8a/ec/2490599550d6c9c97a44c1e36ef4de52d6acf742359eaa385735e30c05c4/fonttools-4.59.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c735e385e30278c54f43a0d056736942023c9043f84ee1021eff9fd616d17693", size = 4951958, upload-time = "2025-08-14T16:26:51.616Z" }, + { url = "https://files.pythonhosted.org/packages/d1/40/bd053f6f7634234a9b9805ff8ae4f32df4f2168bee23cafd1271ba9915a9/fonttools-4.59.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1017413cdc8555dce7ee23720da490282ab7ec1cf022af90a241f33f9a49afc4", size = 4894738, upload-time = "2025-08-14T16:26:53.836Z" }, + { url = "https://files.pythonhosted.org/packages/ac/a1/3cd12a010d288325a7cfcf298a84825f0f9c29b01dee1baba64edfe89257/fonttools-4.59.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5c6d8d773470a5107052874341ed3c487c16ecd179976d81afed89dea5cd7406", size = 5045983, upload-time = "2025-08-14T16:26:56.153Z" }, + { url = "https://files.pythonhosted.org/packages/a2/af/8a2c3f6619cc43cf87951405337cc8460d08a4e717bb05eaa94b335d11dc/fonttools-4.59.1-cp312-cp312-win32.whl", hash = "sha256:2a2d0d33307f6ad3a2086a95dd607c202ea8852fa9fb52af9b48811154d1428a", size = 2203407, upload-time = "2025-08-14T16:26:58.165Z" }, + { url = "https://files.pythonhosted.org/packages/8e/f2/a19b874ddbd3ebcf11d7e25188ef9ac3f68b9219c62263acb34aca8cde05/fonttools-4.59.1-cp312-cp312-win_amd64.whl", hash = "sha256:0b9e4fa7eaf046ed6ac470f6033d52c052481ff7a6e0a92373d14f556f298dc0", size = 2251561, upload-time = "2025-08-14T16:27:00.646Z" }, + { url = "https://files.pythonhosted.org/packages/19/5e/94a4d7f36c36e82f6a81e0064d148542e0ad3e6cf51fc5461ca128f3658d/fonttools-4.59.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:89d9957b54246c6251345297dddf77a84d2c19df96af30d2de24093bbdf0528b", size = 2760192, upload-time = "2025-08-14T16:27:03.024Z" }, + { url = "https://files.pythonhosted.org/packages/ee/a5/f50712fc33ef9d06953c660cefaf8c8fe4b8bc74fa21f44ee5e4f9739439/fonttools-4.59.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8156b11c0d5405810d216f53907bd0f8b982aa5f1e7e3127ab3be1a4062154ff", size = 2332694, upload-time = "2025-08-14T16:27:04.883Z" }, + { url = "https://files.pythonhosted.org/packages/e9/a2/5a9fc21c354bf8613215ce233ab0d933bd17d5ff4c29693636551adbc7b3/fonttools-4.59.1-cp313-cp313-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:8387876a8011caec52d327d5e5bca705d9399ec4b17afb8b431ec50d47c17d23", size = 4889254, upload-time = "2025-08-14T16:27:07.02Z" }, + { url = "https://files.pythonhosted.org/packages/2d/e5/54a6dc811eba018d022ca2e8bd6f2969291f9586ccf9a22a05fc55f91250/fonttools-4.59.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fb13823a74b3a9204a8ed76d3d6d5ec12e64cc5bc44914eb9ff1cdac04facd43", size = 4949109, upload-time = "2025-08-14T16:27:09.3Z" }, + { url = "https://files.pythonhosted.org/packages/db/15/b05c72a248a95bea0fd05fbd95acdf0742945942143fcf961343b7a3663a/fonttools-4.59.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e1ca10da138c300f768bb68e40e5b20b6ecfbd95f91aac4cc15010b6b9d65455", size = 4888428, upload-time = "2025-08-14T16:27:11.514Z" }, + { url = "https://files.pythonhosted.org/packages/63/71/c7d6840f858d695adc0c4371ec45e3fb1c8e060b276ba944e2800495aca4/fonttools-4.59.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:2beb5bfc4887a3130f8625349605a3a45fe345655ce6031d1bac11017454b943", size = 5032668, upload-time = "2025-08-14T16:27:13.872Z" }, + { url = "https://files.pythonhosted.org/packages/90/54/57be4aca6f1312e2bc4d811200dd822325794e05bdb26eeff0976edca651/fonttools-4.59.1-cp313-cp313-win32.whl", hash = "sha256:419f16d750d78e6d704bfe97b48bba2f73b15c9418f817d0cb8a9ca87a5b94bf", size = 2201832, upload-time = "2025-08-14T16:27:16.126Z" }, + { url = "https://files.pythonhosted.org/packages/fc/1f/1899a6175a5f900ed8730a0d64f53ca1b596ed7609bfda033cf659114258/fonttools-4.59.1-cp313-cp313-win_amd64.whl", hash = "sha256:c536f8a852e8d3fa71dde1ec03892aee50be59f7154b533f0bf3c1174cfd5126", size = 2250673, upload-time = "2025-08-14T16:27:18.033Z" }, + { url = "https://files.pythonhosted.org/packages/15/07/f6ba82c22f118d9985c37fea65d8d715ca71300d78b6c6e90874dc59f11d/fonttools-4.59.1-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:d5c3bfdc9663f3d4b565f9cb3b8c1efb3e178186435b45105bde7328cfddd7fe", size = 2758606, upload-time = "2025-08-14T16:27:20.064Z" }, + { url = "https://files.pythonhosted.org/packages/3a/81/84aa3d0ce27b0112c28b67b637ff7a47cf401cf5fbfee6476e4bc9777580/fonttools-4.59.1-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:ea03f1da0d722fe3c2278a05957e6550175571a4894fbf9d178ceef4a3783d2b", size = 2330187, upload-time = "2025-08-14T16:27:22.42Z" }, + { url = "https://files.pythonhosted.org/packages/17/41/b3ba43f78afb321e2e50232c87304c8d0f5ab39b64389b8286cc39cdb824/fonttools-4.59.1-cp314-cp314-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:57a3708ca6bfccb790f585fa6d8f29432ec329618a09ff94c16bcb3c55994643", size = 4832020, upload-time = "2025-08-14T16:27:24.214Z" }, + { url = "https://files.pythonhosted.org/packages/67/b1/3af871c7fb325a68938e7ce544ca48bfd2c6bb7b357f3c8252933b29100a/fonttools-4.59.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:729367c91eb1ee84e61a733acc485065a00590618ca31c438e7dd4d600c01486", size = 4930687, upload-time = "2025-08-14T16:27:26.484Z" }, + { url = "https://files.pythonhosted.org/packages/c5/4f/299fc44646b30d9ef03ffaa78b109c7bd32121f0d8f10009ee73ac4514bc/fonttools-4.59.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:8f8ef66ac6db450193ed150e10b3b45dde7aded10c5d279968bc63368027f62b", size = 4875794, upload-time = "2025-08-14T16:27:28.887Z" }, + { url = "https://files.pythonhosted.org/packages/90/cf/a0a3d763ab58f5f81ceff104ddb662fd9da94248694862b9c6cbd509fdd5/fonttools-4.59.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:075f745d539a998cd92cb84c339a82e53e49114ec62aaea8307c80d3ad3aef3a", size = 4985780, upload-time = "2025-08-14T16:27:30.858Z" }, + { url = "https://files.pythonhosted.org/packages/72/c5/ba76511aaae143d89c29cd32ce30bafb61c477e8759a1590b8483f8065f8/fonttools-4.59.1-cp314-cp314-win32.whl", hash = "sha256:c2b0597522d4c5bb18aa5cf258746a2d4a90f25878cbe865e4d35526abd1b9fc", size = 2205610, upload-time = "2025-08-14T16:27:32.578Z" }, + { url = "https://files.pythonhosted.org/packages/a9/65/b250e69d6caf35bc65cddbf608be0662d741c248f2e7503ab01081fc267e/fonttools-4.59.1-cp314-cp314-win_amd64.whl", hash = "sha256:e9ad4ce044e3236f0814c906ccce8647046cc557539661e35211faadf76f283b", size = 2255376, upload-time = "2025-08-14T16:27:34.653Z" }, + { url = "https://files.pythonhosted.org/packages/11/f3/0bc63a23ac0f8175e23d82f85d6ee693fbd849de7ad739f0a3622182ad29/fonttools-4.59.1-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:652159e8214eb4856e8387ebcd6b6bd336ee258cbeb639c8be52005b122b9609", size = 2826546, upload-time = "2025-08-14T16:27:36.783Z" }, + { url = "https://files.pythonhosted.org/packages/e9/46/a3968205590e068fdf60e926be329a207782576cb584d3b7dcd2d2844957/fonttools-4.59.1-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:43d177cd0e847ea026fedd9f099dc917da136ed8792d142298a252836390c478", size = 2359771, upload-time = "2025-08-14T16:27:39.678Z" }, + { url = "https://files.pythonhosted.org/packages/b8/ff/d14b4c283879e8cb57862d9624a34fe6522b6fcdd46ccbfc58900958794a/fonttools-4.59.1-cp314-cp314t-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:e54437651e1440ee53a95e6ceb6ee440b67a3d348c76f45f4f48de1a5ecab019", size = 4831575, upload-time = "2025-08-14T16:27:41.885Z" }, + { url = "https://files.pythonhosted.org/packages/9c/04/a277d9a584a49d98ca12d3b2c6663bdf333ae97aaa83bd0cdabf7c5a6c84/fonttools-4.59.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6065fdec8ff44c32a483fd44abe5bcdb40dd5e2571a5034b555348f2b3a52cea", size = 5069962, upload-time = "2025-08-14T16:27:44.284Z" }, + { url = "https://files.pythonhosted.org/packages/16/6f/3d2ae69d96c4cdee6dfe7598ca5519a1514487700ca3d7c49c5a1ad65308/fonttools-4.59.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:42052b56d176f8b315fbc09259439c013c0cb2109df72447148aeda677599612", size = 4942926, upload-time = "2025-08-14T16:27:46.523Z" }, + { url = "https://files.pythonhosted.org/packages/0c/d3/c17379e0048d03ce26b38e4ab0e9a98280395b00529e093fe2d663ac0658/fonttools-4.59.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:bcd52eaa5c4c593ae9f447c1d13e7e4a00ca21d755645efa660b6999425b3c88", size = 4958678, upload-time = "2025-08-14T16:27:48.555Z" }, + { url = "https://files.pythonhosted.org/packages/8c/3f/c5543a1540abdfb4d375e3ebeb84de365ab9b153ec14cb7db05f537dd1e7/fonttools-4.59.1-cp314-cp314t-win32.whl", hash = "sha256:02e4fdf27c550dded10fe038a5981c29f81cb9bc649ff2eaa48e80dab8998f97", size = 2266706, upload-time = "2025-08-14T16:27:50.556Z" }, + { url = "https://files.pythonhosted.org/packages/3e/99/85bff6e674226bc8402f983e365f07e76d990e7220ba72bcc738fef52391/fonttools-4.59.1-cp314-cp314t-win_amd64.whl", hash = "sha256:412a5fd6345872a7c249dac5bcce380393f40c1c316ac07f447bc17d51900922", size = 2329994, upload-time = "2025-08-14T16:27:52.36Z" }, + { url = "https://files.pythonhosted.org/packages/0f/64/9d606e66d498917cd7a2ff24f558010d42d6fd4576d9dd57f0bd98333f5a/fonttools-4.59.1-py3-none-any.whl", hash = "sha256:647db657073672a8330608970a984d51573557f328030566521bc03415535042", size = 1130094, upload-time = "2025-08-14T16:28:12.048Z" }, +] + +[[package]] +name = "fsspec" +version = "2025.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/8b/02/0835e6ab9cfc03916fe3f78c0956cfcdb6ff2669ffa6651065d5ebf7fc98/fsspec-2025.7.0.tar.gz", hash = "sha256:786120687ffa54b8283d942929540d8bc5ccfa820deb555a2b5d0ed2b737bf58", size = 304432, upload-time = "2025-07-15T16:05:21.19Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2f/e0/014d5d9d7a4564cf1c40b5039bc882db69fd881111e03ab3657ac0b218e2/fsspec-2025.7.0-py3-none-any.whl", hash = "sha256:8b012e39f63c7d5f10474de957f3ab793b47b45ae7d39f2fb735f8bbe25c0e21", size = 199597, upload-time = "2025-07-15T16:05:19.529Z" }, +] + +[[package]] +name = "h11" +version = "0.16.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1", size = 101250, upload-time = "2025-04-24T03:35:25.427Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" }, +] + +[[package]] +name = "hydrus-cli" +version = "0.1.0" +source = { virtual = "tools/hydrus_cli" } +dependencies = [ + { name = "typer" }, +] + +[package.metadata] +requires-dist = [{ name = "typer", specifier = ">=0.16.1" }] + +[[package]] +name = "idna" +version = "3.10" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490, upload-time = "2024-09-15T18:07:39.745Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442, upload-time = "2024-09-15T18:07:37.964Z" }, +] + +[[package]] +name = "jinja2" +version = "3.1.6" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markupsafe" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/df/bf/f7da0350254c0ed7c72f3e33cef02e048281fec7ecec5f032d4aac52226b/jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d", size = 245115, upload-time = "2025-03-05T20:05:02.478Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899, upload-time = "2025-03-05T20:05:00.369Z" }, +] + +[[package]] +name = "kiwisolver" +version = "1.4.9" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/5c/3c/85844f1b0feb11ee581ac23fe5fce65cd049a200c1446708cc1b7f922875/kiwisolver-1.4.9.tar.gz", hash = "sha256:c3b22c26c6fd6811b0ae8363b95ca8ce4ea3c202d3d0975b2914310ceb1bcc4d", size = 97564, upload-time = "2025-08-10T21:27:49.279Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/86/c9/13573a747838aeb1c76e3267620daa054f4152444d1f3d1a2324b78255b5/kiwisolver-1.4.9-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:ac5a486ac389dddcc5bef4f365b6ae3ffff2c433324fb38dd35e3fab7c957999", size = 123686, upload-time = "2025-08-10T21:26:10.034Z" }, + { url = "https://files.pythonhosted.org/packages/51/ea/2ecf727927f103ffd1739271ca19c424d0e65ea473fbaeea1c014aea93f6/kiwisolver-1.4.9-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f2ba92255faa7309d06fe44c3a4a97efe1c8d640c2a79a5ef728b685762a6fd2", size = 66460, upload-time = "2025-08-10T21:26:11.083Z" }, + { url = "https://files.pythonhosted.org/packages/5b/5a/51f5464373ce2aeb5194508298a508b6f21d3867f499556263c64c621914/kiwisolver-1.4.9-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4a2899935e724dd1074cb568ce7ac0dce28b2cd6ab539c8e001a8578eb106d14", size = 64952, upload-time = "2025-08-10T21:26:12.058Z" }, + { url = "https://files.pythonhosted.org/packages/70/90/6d240beb0f24b74371762873e9b7f499f1e02166a2d9c5801f4dbf8fa12e/kiwisolver-1.4.9-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f6008a4919fdbc0b0097089f67a1eb55d950ed7e90ce2cc3e640abadd2757a04", size = 1474756, upload-time = "2025-08-10T21:26:13.096Z" }, + { url = "https://files.pythonhosted.org/packages/12/42/f36816eaf465220f683fb711efdd1bbf7a7005a2473d0e4ed421389bd26c/kiwisolver-1.4.9-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:67bb8b474b4181770f926f7b7d2f8c0248cbcb78b660fdd41a47054b28d2a752", size = 1276404, upload-time = "2025-08-10T21:26:14.457Z" }, + { url = "https://files.pythonhosted.org/packages/2e/64/bc2de94800adc830c476dce44e9b40fd0809cddeef1fde9fcf0f73da301f/kiwisolver-1.4.9-cp312-cp312-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2327a4a30d3ee07d2fbe2e7933e8a37c591663b96ce42a00bc67461a87d7df77", size = 1294410, upload-time = "2025-08-10T21:26:15.73Z" }, + { url = "https://files.pythonhosted.org/packages/5f/42/2dc82330a70aa8e55b6d395b11018045e58d0bb00834502bf11509f79091/kiwisolver-1.4.9-cp312-cp312-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:7a08b491ec91b1d5053ac177afe5290adacf1f0f6307d771ccac5de30592d198", size = 1343631, upload-time = "2025-08-10T21:26:17.045Z" }, + { url = "https://files.pythonhosted.org/packages/22/fd/f4c67a6ed1aab149ec5a8a401c323cee7a1cbe364381bb6c9c0d564e0e20/kiwisolver-1.4.9-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d8fc5c867c22b828001b6a38d2eaeb88160bf5783c6cb4a5e440efc981ce286d", size = 2224963, upload-time = "2025-08-10T21:26:18.737Z" }, + { url = "https://files.pythonhosted.org/packages/45/aa/76720bd4cb3713314677d9ec94dcc21ced3f1baf4830adde5bb9b2430a5f/kiwisolver-1.4.9-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:3b3115b2581ea35bb6d1f24a4c90af37e5d9b49dcff267eeed14c3893c5b86ab", size = 2321295, upload-time = "2025-08-10T21:26:20.11Z" }, + { url = "https://files.pythonhosted.org/packages/80/19/d3ec0d9ab711242f56ae0dc2fc5d70e298bb4a1f9dfab44c027668c673a1/kiwisolver-1.4.9-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:858e4c22fb075920b96a291928cb7dea5644e94c0ee4fcd5af7e865655e4ccf2", size = 2487987, upload-time = "2025-08-10T21:26:21.49Z" }, + { url = "https://files.pythonhosted.org/packages/39/e9/61e4813b2c97e86b6fdbd4dd824bf72d28bcd8d4849b8084a357bc0dd64d/kiwisolver-1.4.9-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ed0fecd28cc62c54b262e3736f8bb2512d8dcfdc2bcf08be5f47f96bf405b145", size = 2291817, upload-time = "2025-08-10T21:26:22.812Z" }, + { url = "https://files.pythonhosted.org/packages/a0/41/85d82b0291db7504da3c2defe35c9a8a5c9803a730f297bd823d11d5fb77/kiwisolver-1.4.9-cp312-cp312-win_amd64.whl", hash = "sha256:f68208a520c3d86ea51acf688a3e3002615a7f0238002cccc17affecc86a8a54", size = 73895, upload-time = "2025-08-10T21:26:24.37Z" }, + { url = "https://files.pythonhosted.org/packages/e2/92/5f3068cf15ee5cb624a0c7596e67e2a0bb2adee33f71c379054a491d07da/kiwisolver-1.4.9-cp312-cp312-win_arm64.whl", hash = "sha256:2c1a4f57df73965f3f14df20b80ee29e6a7930a57d2d9e8491a25f676e197c60", size = 64992, upload-time = "2025-08-10T21:26:25.732Z" }, + { url = "https://files.pythonhosted.org/packages/31/c1/c2686cda909742ab66c7388e9a1a8521a59eb89f8bcfbee28fc980d07e24/kiwisolver-1.4.9-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:a5d0432ccf1c7ab14f9949eec60c5d1f924f17c037e9f8b33352fa05799359b8", size = 123681, upload-time = "2025-08-10T21:26:26.725Z" }, + { url = "https://files.pythonhosted.org/packages/ca/f0/f44f50c9f5b1a1860261092e3bc91ecdc9acda848a8b8c6abfda4a24dd5c/kiwisolver-1.4.9-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efb3a45b35622bb6c16dbfab491a8f5a391fe0e9d45ef32f4df85658232ca0e2", size = 66464, upload-time = "2025-08-10T21:26:27.733Z" }, + { url = "https://files.pythonhosted.org/packages/2d/7a/9d90a151f558e29c3936b8a47ac770235f436f2120aca41a6d5f3d62ae8d/kiwisolver-1.4.9-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1a12cf6398e8a0a001a059747a1cbf24705e18fe413bc22de7b3d15c67cffe3f", size = 64961, upload-time = "2025-08-10T21:26:28.729Z" }, + { url = "https://files.pythonhosted.org/packages/e9/e9/f218a2cb3a9ffbe324ca29a9e399fa2d2866d7f348ec3a88df87fc248fc5/kiwisolver-1.4.9-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:b67e6efbf68e077dd71d1a6b37e43e1a99d0bff1a3d51867d45ee8908b931098", size = 1474607, upload-time = "2025-08-10T21:26:29.798Z" }, + { url = "https://files.pythonhosted.org/packages/d9/28/aac26d4c882f14de59041636292bc838db8961373825df23b8eeb807e198/kiwisolver-1.4.9-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5656aa670507437af0207645273ccdfee4f14bacd7f7c67a4306d0dcaeaf6eed", size = 1276546, upload-time = "2025-08-10T21:26:31.401Z" }, + { url = "https://files.pythonhosted.org/packages/8b/ad/8bfc1c93d4cc565e5069162f610ba2f48ff39b7de4b5b8d93f69f30c4bed/kiwisolver-1.4.9-cp313-cp313-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:bfc08add558155345129c7803b3671cf195e6a56e7a12f3dde7c57d9b417f525", size = 1294482, upload-time = "2025-08-10T21:26:32.721Z" }, + { url = "https://files.pythonhosted.org/packages/da/f1/6aca55ff798901d8ce403206d00e033191f63d82dd708a186e0ed2067e9c/kiwisolver-1.4.9-cp313-cp313-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:40092754720b174e6ccf9e845d0d8c7d8e12c3d71e7fc35f55f3813e96376f78", size = 1343720, upload-time = "2025-08-10T21:26:34.032Z" }, + { url = "https://files.pythonhosted.org/packages/d1/91/eed031876c595c81d90d0f6fc681ece250e14bf6998c3d7c419466b523b7/kiwisolver-1.4.9-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:497d05f29a1300d14e02e6441cf0f5ee81c1ff5a304b0d9fb77423974684e08b", size = 2224907, upload-time = "2025-08-10T21:26:35.824Z" }, + { url = "https://files.pythonhosted.org/packages/e9/ec/4d1925f2e49617b9cca9c34bfa11adefad49d00db038e692a559454dfb2e/kiwisolver-1.4.9-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:bdd1a81a1860476eb41ac4bc1e07b3f07259e6d55bbf739b79c8aaedcf512799", size = 2321334, upload-time = "2025-08-10T21:26:37.534Z" }, + { url = "https://files.pythonhosted.org/packages/43/cb/450cd4499356f68802750c6ddc18647b8ea01ffa28f50d20598e0befe6e9/kiwisolver-1.4.9-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:e6b93f13371d341afee3be9f7c5964e3fe61d5fa30f6a30eb49856935dfe4fc3", size = 2488313, upload-time = "2025-08-10T21:26:39.191Z" }, + { url = "https://files.pythonhosted.org/packages/71/67/fc76242bd99f885651128a5d4fa6083e5524694b7c88b489b1b55fdc491d/kiwisolver-1.4.9-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:d75aa530ccfaa593da12834b86a0724f58bff12706659baa9227c2ccaa06264c", size = 2291970, upload-time = "2025-08-10T21:26:40.828Z" }, + { url = "https://files.pythonhosted.org/packages/75/bd/f1a5d894000941739f2ae1b65a32892349423ad49c2e6d0771d0bad3fae4/kiwisolver-1.4.9-cp313-cp313-win_amd64.whl", hash = "sha256:dd0a578400839256df88c16abddf9ba14813ec5f21362e1fe65022e00c883d4d", size = 73894, upload-time = "2025-08-10T21:26:42.33Z" }, + { url = "https://files.pythonhosted.org/packages/95/38/dce480814d25b99a391abbddadc78f7c117c6da34be68ca8b02d5848b424/kiwisolver-1.4.9-cp313-cp313-win_arm64.whl", hash = "sha256:d4188e73af84ca82468f09cadc5ac4db578109e52acb4518d8154698d3a87ca2", size = 64995, upload-time = "2025-08-10T21:26:43.889Z" }, + { url = "https://files.pythonhosted.org/packages/e2/37/7d218ce5d92dadc5ebdd9070d903e0c7cf7edfe03f179433ac4d13ce659c/kiwisolver-1.4.9-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:5a0f2724dfd4e3b3ac5a82436a8e6fd16baa7d507117e4279b660fe8ca38a3a1", size = 126510, upload-time = "2025-08-10T21:26:44.915Z" }, + { url = "https://files.pythonhosted.org/packages/23/b0/e85a2b48233daef4b648fb657ebbb6f8367696a2d9548a00b4ee0eb67803/kiwisolver-1.4.9-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:1b11d6a633e4ed84fc0ddafd4ebfd8ea49b3f25082c04ad12b8315c11d504dc1", size = 67903, upload-time = "2025-08-10T21:26:45.934Z" }, + { url = "https://files.pythonhosted.org/packages/44/98/f2425bc0113ad7de24da6bb4dae1343476e95e1d738be7c04d31a5d037fd/kiwisolver-1.4.9-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:61874cdb0a36016354853593cffc38e56fc9ca5aa97d2c05d3dcf6922cd55a11", size = 66402, upload-time = "2025-08-10T21:26:47.101Z" }, + { url = "https://files.pythonhosted.org/packages/98/d8/594657886df9f34c4177cc353cc28ca7e6e5eb562d37ccc233bff43bbe2a/kiwisolver-1.4.9-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:60c439763a969a6af93b4881db0eed8fadf93ee98e18cbc35bc8da868d0c4f0c", size = 1582135, upload-time = "2025-08-10T21:26:48.665Z" }, + { url = "https://files.pythonhosted.org/packages/5c/c6/38a115b7170f8b306fc929e166340c24958347308ea3012c2b44e7e295db/kiwisolver-1.4.9-cp313-cp313t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:92a2f997387a1b79a75e7803aa7ded2cfbe2823852ccf1ba3bcf613b62ae3197", size = 1389409, upload-time = "2025-08-10T21:26:50.335Z" }, + { url = "https://files.pythonhosted.org/packages/bf/3b/e04883dace81f24a568bcee6eb3001da4ba05114afa622ec9b6fafdc1f5e/kiwisolver-1.4.9-cp313-cp313t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a31d512c812daea6d8b3be3b2bfcbeb091dbb09177706569bcfc6240dcf8b41c", size = 1401763, upload-time = "2025-08-10T21:26:51.867Z" }, + { url = "https://files.pythonhosted.org/packages/9f/80/20ace48e33408947af49d7d15c341eaee69e4e0304aab4b7660e234d6288/kiwisolver-1.4.9-cp313-cp313t-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:52a15b0f35dad39862d376df10c5230155243a2c1a436e39eb55623ccbd68185", size = 1453643, upload-time = "2025-08-10T21:26:53.592Z" }, + { url = "https://files.pythonhosted.org/packages/64/31/6ce4380a4cd1f515bdda976a1e90e547ccd47b67a1546d63884463c92ca9/kiwisolver-1.4.9-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:a30fd6fdef1430fd9e1ba7b3398b5ee4e2887783917a687d86ba69985fb08748", size = 2330818, upload-time = "2025-08-10T21:26:55.051Z" }, + { url = "https://files.pythonhosted.org/packages/fa/e9/3f3fcba3bcc7432c795b82646306e822f3fd74df0ee81f0fa067a1f95668/kiwisolver-1.4.9-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:cc9617b46837c6468197b5945e196ee9ca43057bb7d9d1ae688101e4e1dddf64", size = 2419963, upload-time = "2025-08-10T21:26:56.421Z" }, + { url = "https://files.pythonhosted.org/packages/99/43/7320c50e4133575c66e9f7dadead35ab22d7c012a3b09bb35647792b2a6d/kiwisolver-1.4.9-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:0ab74e19f6a2b027ea4f845a78827969af45ce790e6cb3e1ebab71bdf9f215ff", size = 2594639, upload-time = "2025-08-10T21:26:57.882Z" }, + { url = "https://files.pythonhosted.org/packages/65/d6/17ae4a270d4a987ef8a385b906d2bdfc9fce502d6dc0d3aea865b47f548c/kiwisolver-1.4.9-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:dba5ee5d3981160c28d5490f0d1b7ed730c22470ff7f6cc26cfcfaacb9896a07", size = 2391741, upload-time = "2025-08-10T21:26:59.237Z" }, + { url = "https://files.pythonhosted.org/packages/2a/8f/8f6f491d595a9e5912971f3f863d81baddccc8a4d0c3749d6a0dd9ffc9df/kiwisolver-1.4.9-cp313-cp313t-win_arm64.whl", hash = "sha256:0749fd8f4218ad2e851e11cc4dc05c7cbc0cbc4267bdfdb31782e65aace4ee9c", size = 68646, upload-time = "2025-08-10T21:27:00.52Z" }, + { url = "https://files.pythonhosted.org/packages/6b/32/6cc0fbc9c54d06c2969faa9c1d29f5751a2e51809dd55c69055e62d9b426/kiwisolver-1.4.9-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:9928fe1eb816d11ae170885a74d074f57af3a0d65777ca47e9aeb854a1fba386", size = 123806, upload-time = "2025-08-10T21:27:01.537Z" }, + { url = "https://files.pythonhosted.org/packages/b2/dd/2bfb1d4a4823d92e8cbb420fe024b8d2167f72079b3bb941207c42570bdf/kiwisolver-1.4.9-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:d0005b053977e7b43388ddec89fa567f43d4f6d5c2c0affe57de5ebf290dc552", size = 66605, upload-time = "2025-08-10T21:27:03.335Z" }, + { url = "https://files.pythonhosted.org/packages/f7/69/00aafdb4e4509c2ca6064646cba9cd4b37933898f426756adb2cb92ebbed/kiwisolver-1.4.9-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:2635d352d67458b66fd0667c14cb1d4145e9560d503219034a18a87e971ce4f3", size = 64925, upload-time = "2025-08-10T21:27:04.339Z" }, + { url = "https://files.pythonhosted.org/packages/43/dc/51acc6791aa14e5cb6d8a2e28cefb0dc2886d8862795449d021334c0df20/kiwisolver-1.4.9-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:767c23ad1c58c9e827b649a9ab7809fd5fd9db266a9cf02b0e926ddc2c680d58", size = 1472414, upload-time = "2025-08-10T21:27:05.437Z" }, + { url = "https://files.pythonhosted.org/packages/3d/bb/93fa64a81db304ac8a246f834d5094fae4b13baf53c839d6bb6e81177129/kiwisolver-1.4.9-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:72d0eb9fba308b8311685c2268cf7d0a0639a6cd027d8128659f72bdd8a024b4", size = 1281272, upload-time = "2025-08-10T21:27:07.063Z" }, + { url = "https://files.pythonhosted.org/packages/70/e6/6df102916960fb8d05069d4bd92d6d9a8202d5a3e2444494e7cd50f65b7a/kiwisolver-1.4.9-cp314-cp314-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f68e4f3eeca8fb22cc3d731f9715a13b652795ef657a13df1ad0c7dc0e9731df", size = 1298578, upload-time = "2025-08-10T21:27:08.452Z" }, + { url = "https://files.pythonhosted.org/packages/7c/47/e142aaa612f5343736b087864dbaebc53ea8831453fb47e7521fa8658f30/kiwisolver-1.4.9-cp314-cp314-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d84cd4061ae292d8ac367b2c3fa3aad11cb8625a95d135fe93f286f914f3f5a6", size = 1345607, upload-time = "2025-08-10T21:27:10.125Z" }, + { url = "https://files.pythonhosted.org/packages/54/89/d641a746194a0f4d1a3670fb900d0dbaa786fb98341056814bc3f058fa52/kiwisolver-1.4.9-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:a60ea74330b91bd22a29638940d115df9dc00af5035a9a2a6ad9399ffb4ceca5", size = 2230150, upload-time = "2025-08-10T21:27:11.484Z" }, + { url = "https://files.pythonhosted.org/packages/aa/6b/5ee1207198febdf16ac11f78c5ae40861b809cbe0e6d2a8d5b0b3044b199/kiwisolver-1.4.9-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:ce6a3a4e106cf35c2d9c4fa17c05ce0b180db622736845d4315519397a77beaf", size = 2325979, upload-time = "2025-08-10T21:27:12.917Z" }, + { url = "https://files.pythonhosted.org/packages/fc/ff/b269eefd90f4ae14dcc74973d5a0f6d28d3b9bb1afd8c0340513afe6b39a/kiwisolver-1.4.9-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:77937e5e2a38a7b48eef0585114fe7930346993a88060d0bf886086d2aa49ef5", size = 2491456, upload-time = "2025-08-10T21:27:14.353Z" }, + { url = "https://files.pythonhosted.org/packages/fc/d4/10303190bd4d30de547534601e259a4fbf014eed94aae3e5521129215086/kiwisolver-1.4.9-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:24c175051354f4a28c5d6a31c93906dc653e2bf234e8a4bbfb964892078898ce", size = 2294621, upload-time = "2025-08-10T21:27:15.808Z" }, + { url = "https://files.pythonhosted.org/packages/28/e0/a9a90416fce5c0be25742729c2ea52105d62eda6c4be4d803c2a7be1fa50/kiwisolver-1.4.9-cp314-cp314-win_amd64.whl", hash = "sha256:0763515d4df10edf6d06a3c19734e2566368980d21ebec439f33f9eb936c07b7", size = 75417, upload-time = "2025-08-10T21:27:17.436Z" }, + { url = "https://files.pythonhosted.org/packages/1f/10/6949958215b7a9a264299a7db195564e87900f709db9245e4ebdd3c70779/kiwisolver-1.4.9-cp314-cp314-win_arm64.whl", hash = "sha256:0e4e2bf29574a6a7b7f6cb5fa69293b9f96c928949ac4a53ba3f525dffb87f9c", size = 66582, upload-time = "2025-08-10T21:27:18.436Z" }, + { url = "https://files.pythonhosted.org/packages/ec/79/60e53067903d3bc5469b369fe0dfc6b3482e2133e85dae9daa9527535991/kiwisolver-1.4.9-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:d976bbb382b202f71c67f77b0ac11244021cfa3f7dfd9e562eefcea2df711548", size = 126514, upload-time = "2025-08-10T21:27:19.465Z" }, + { url = "https://files.pythonhosted.org/packages/25/d1/4843d3e8d46b072c12a38c97c57fab4608d36e13fe47d47ee96b4d61ba6f/kiwisolver-1.4.9-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:2489e4e5d7ef9a1c300a5e0196e43d9c739f066ef23270607d45aba368b91f2d", size = 67905, upload-time = "2025-08-10T21:27:20.51Z" }, + { url = "https://files.pythonhosted.org/packages/8c/ae/29ffcbd239aea8b93108de1278271ae764dfc0d803a5693914975f200596/kiwisolver-1.4.9-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:e2ea9f7ab7fbf18fffb1b5434ce7c69a07582f7acc7717720f1d69f3e806f90c", size = 66399, upload-time = "2025-08-10T21:27:21.496Z" }, + { url = "https://files.pythonhosted.org/packages/a1/ae/d7ba902aa604152c2ceba5d352d7b62106bedbccc8e95c3934d94472bfa3/kiwisolver-1.4.9-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:b34e51affded8faee0dfdb705416153819d8ea9250bbbf7ea1b249bdeb5f1122", size = 1582197, upload-time = "2025-08-10T21:27:22.604Z" }, + { url = "https://files.pythonhosted.org/packages/f2/41/27c70d427eddb8bc7e4f16420a20fefc6f480312122a59a959fdfe0445ad/kiwisolver-1.4.9-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d8aacd3d4b33b772542b2e01beb50187536967b514b00003bdda7589722d2a64", size = 1390125, upload-time = "2025-08-10T21:27:24.036Z" }, + { url = "https://files.pythonhosted.org/packages/41/42/b3799a12bafc76d962ad69083f8b43b12bf4fe78b097b12e105d75c9b8f1/kiwisolver-1.4.9-cp314-cp314t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7cf974dd4e35fa315563ac99d6287a1024e4dc2077b8a7d7cd3d2fb65d283134", size = 1402612, upload-time = "2025-08-10T21:27:25.773Z" }, + { url = "https://files.pythonhosted.org/packages/d2/b5/a210ea073ea1cfaca1bb5c55a62307d8252f531beb364e18aa1e0888b5a0/kiwisolver-1.4.9-cp314-cp314t-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:85bd218b5ecfbee8c8a82e121802dcb519a86044c9c3b2e4aef02fa05c6da370", size = 1453990, upload-time = "2025-08-10T21:27:27.089Z" }, + { url = "https://files.pythonhosted.org/packages/5f/ce/a829eb8c033e977d7ea03ed32fb3c1781b4fa0433fbadfff29e39c676f32/kiwisolver-1.4.9-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:0856e241c2d3df4efef7c04a1e46b1936b6120c9bcf36dd216e3acd84bc4fb21", size = 2331601, upload-time = "2025-08-10T21:27:29.343Z" }, + { url = "https://files.pythonhosted.org/packages/e0/4b/b5e97eb142eb9cd0072dacfcdcd31b1c66dc7352b0f7c7255d339c0edf00/kiwisolver-1.4.9-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:9af39d6551f97d31a4deebeac6f45b156f9755ddc59c07b402c148f5dbb6482a", size = 2422041, upload-time = "2025-08-10T21:27:30.754Z" }, + { url = "https://files.pythonhosted.org/packages/40/be/8eb4cd53e1b85ba4edc3a9321666f12b83113a178845593307a3e7891f44/kiwisolver-1.4.9-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:bb4ae2b57fc1d8cbd1cf7b1d9913803681ffa903e7488012be5b76dedf49297f", size = 2594897, upload-time = "2025-08-10T21:27:32.803Z" }, + { url = "https://files.pythonhosted.org/packages/99/dd/841e9a66c4715477ea0abc78da039832fbb09dac5c35c58dc4c41a407b8a/kiwisolver-1.4.9-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:aedff62918805fb62d43a4aa2ecd4482c380dc76cd31bd7c8878588a61bd0369", size = 2391835, upload-time = "2025-08-10T21:27:34.23Z" }, + { url = "https://files.pythonhosted.org/packages/0c/28/4b2e5c47a0da96896fdfdb006340ade064afa1e63675d01ea5ac222b6d52/kiwisolver-1.4.9-cp314-cp314t-win_amd64.whl", hash = "sha256:1fa333e8b2ce4d9660f2cda9c0e1b6bafcfb2457a9d259faa82289e73ec24891", size = 79988, upload-time = "2025-08-10T21:27:35.587Z" }, + { url = "https://files.pythonhosted.org/packages/80/be/3578e8afd18c88cdf9cb4cffde75a96d2be38c5a903f1ed0ceec061bd09e/kiwisolver-1.4.9-cp314-cp314t-win_arm64.whl", hash = "sha256:4a48a2ce79d65d363597ef7b567ce3d14d68783d2b2263d98db3d9477805ba32", size = 70260, upload-time = "2025-08-10T21:27:36.606Z" }, +] + +[[package]] +name = "markdown-it-py" +version = "4.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mdurl" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/5b/f5/4ec618ed16cc4f8fb3b701563655a69816155e79e24a17b651541804721d/markdown_it_py-4.0.0.tar.gz", hash = "sha256:cb0a2b4aa34f932c007117b194e945bd74e0ec24133ceb5bac59009cda1cb9f3", size = 73070, upload-time = "2025-08-11T12:57:52.854Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/94/54/e7d793b573f298e1c9013b8c4dade17d481164aa517d1d7148619c2cedbf/markdown_it_py-4.0.0-py3-none-any.whl", hash = "sha256:87327c59b172c5011896038353a81343b6754500a08cd7a4973bb48c6d578147", size = 87321, upload-time = "2025-08-11T12:57:51.923Z" }, +] + +[[package]] +name = "markupsafe" +version = "3.0.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b2/97/5d42485e71dfc078108a86d6de8fa46db44a1a9295e89c5d6d4a06e23a62/markupsafe-3.0.2.tar.gz", hash = "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0", size = 20537, upload-time = "2024-10-18T15:21:54.129Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/22/09/d1f21434c97fc42f09d290cbb6350d44eb12f09cc62c9476effdb33a18aa/MarkupSafe-3.0.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:9778bd8ab0a994ebf6f84c2b949e65736d5575320a17ae8984a77fab08db94cf", size = 14274, upload-time = "2024-10-18T15:21:13.777Z" }, + { url = "https://files.pythonhosted.org/packages/6b/b0/18f76bba336fa5aecf79d45dcd6c806c280ec44538b3c13671d49099fdd0/MarkupSafe-3.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:846ade7b71e3536c4e56b386c2a47adf5741d2d8b94ec9dc3e92e5e1ee1e2225", size = 12348, upload-time = "2024-10-18T15:21:14.822Z" }, + { url = "https://files.pythonhosted.org/packages/e0/25/dd5c0f6ac1311e9b40f4af06c78efde0f3b5cbf02502f8ef9501294c425b/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1c99d261bd2d5f6b59325c92c73df481e05e57f19837bdca8413b9eac4bd8028", size = 24149, upload-time = "2024-10-18T15:21:15.642Z" }, + { url = "https://files.pythonhosted.org/packages/f3/f0/89e7aadfb3749d0f52234a0c8c7867877876e0a20b60e2188e9850794c17/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e17c96c14e19278594aa4841ec148115f9c7615a47382ecb6b82bd8fea3ab0c8", size = 23118, upload-time = "2024-10-18T15:21:17.133Z" }, + { url = "https://files.pythonhosted.org/packages/d5/da/f2eeb64c723f5e3777bc081da884b414671982008c47dcc1873d81f625b6/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:88416bd1e65dcea10bc7569faacb2c20ce071dd1f87539ca2ab364bf6231393c", size = 22993, upload-time = "2024-10-18T15:21:18.064Z" }, + { url = "https://files.pythonhosted.org/packages/da/0e/1f32af846df486dce7c227fe0f2398dc7e2e51d4a370508281f3c1c5cddc/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2181e67807fc2fa785d0592dc2d6206c019b9502410671cc905d132a92866557", size = 24178, upload-time = "2024-10-18T15:21:18.859Z" }, + { url = "https://files.pythonhosted.org/packages/c4/f6/bb3ca0532de8086cbff5f06d137064c8410d10779c4c127e0e47d17c0b71/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:52305740fe773d09cffb16f8ed0427942901f00adedac82ec8b67752f58a1b22", size = 23319, upload-time = "2024-10-18T15:21:19.671Z" }, + { url = "https://files.pythonhosted.org/packages/a2/82/8be4c96ffee03c5b4a034e60a31294daf481e12c7c43ab8e34a1453ee48b/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ad10d3ded218f1039f11a75f8091880239651b52e9bb592ca27de44eed242a48", size = 23352, upload-time = "2024-10-18T15:21:20.971Z" }, + { url = "https://files.pythonhosted.org/packages/51/ae/97827349d3fcffee7e184bdf7f41cd6b88d9919c80f0263ba7acd1bbcb18/MarkupSafe-3.0.2-cp312-cp312-win32.whl", hash = "sha256:0f4ca02bea9a23221c0182836703cbf8930c5e9454bacce27e767509fa286a30", size = 15097, upload-time = "2024-10-18T15:21:22.646Z" }, + { url = "https://files.pythonhosted.org/packages/c1/80/a61f99dc3a936413c3ee4e1eecac96c0da5ed07ad56fd975f1a9da5bc630/MarkupSafe-3.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:8e06879fc22a25ca47312fbe7c8264eb0b662f6db27cb2d3bbbc74b1df4b9b87", size = 15601, upload-time = "2024-10-18T15:21:23.499Z" }, + { url = "https://files.pythonhosted.org/packages/83/0e/67eb10a7ecc77a0c2bbe2b0235765b98d164d81600746914bebada795e97/MarkupSafe-3.0.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ba9527cdd4c926ed0760bc301f6728ef34d841f405abf9d4f959c478421e4efd", size = 14274, upload-time = "2024-10-18T15:21:24.577Z" }, + { url = "https://files.pythonhosted.org/packages/2b/6d/9409f3684d3335375d04e5f05744dfe7e9f120062c9857df4ab490a1031a/MarkupSafe-3.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f8b3d067f2e40fe93e1ccdd6b2e1d16c43140e76f02fb1319a05cf2b79d99430", size = 12352, upload-time = "2024-10-18T15:21:25.382Z" }, + { url = "https://files.pythonhosted.org/packages/d2/f5/6eadfcd3885ea85fe2a7c128315cc1bb7241e1987443d78c8fe712d03091/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:569511d3b58c8791ab4c2e1285575265991e6d8f8700c7be0e88f86cb0672094", size = 24122, upload-time = "2024-10-18T15:21:26.199Z" }, + { url = "https://files.pythonhosted.org/packages/0c/91/96cf928db8236f1bfab6ce15ad070dfdd02ed88261c2afafd4b43575e9e9/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15ab75ef81add55874e7ab7055e9c397312385bd9ced94920f2802310c930396", size = 23085, upload-time = "2024-10-18T15:21:27.029Z" }, + { url = "https://files.pythonhosted.org/packages/c2/cf/c9d56af24d56ea04daae7ac0940232d31d5a8354f2b457c6d856b2057d69/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f3818cb119498c0678015754eba762e0d61e5b52d34c8b13d770f0719f7b1d79", size = 22978, upload-time = "2024-10-18T15:21:27.846Z" }, + { url = "https://files.pythonhosted.org/packages/2a/9f/8619835cd6a711d6272d62abb78c033bda638fdc54c4e7f4272cf1c0962b/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cdb82a876c47801bb54a690c5ae105a46b392ac6099881cdfb9f6e95e4014c6a", size = 24208, upload-time = "2024-10-18T15:21:28.744Z" }, + { url = "https://files.pythonhosted.org/packages/f9/bf/176950a1792b2cd2102b8ffeb5133e1ed984547b75db47c25a67d3359f77/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:cabc348d87e913db6ab4aa100f01b08f481097838bdddf7c7a84b7575b7309ca", size = 23357, upload-time = "2024-10-18T15:21:29.545Z" }, + { url = "https://files.pythonhosted.org/packages/ce/4f/9a02c1d335caabe5c4efb90e1b6e8ee944aa245c1aaaab8e8a618987d816/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:444dcda765c8a838eaae23112db52f1efaf750daddb2d9ca300bcae1039adc5c", size = 23344, upload-time = "2024-10-18T15:21:30.366Z" }, + { url = "https://files.pythonhosted.org/packages/ee/55/c271b57db36f748f0e04a759ace9f8f759ccf22b4960c270c78a394f58be/MarkupSafe-3.0.2-cp313-cp313-win32.whl", hash = "sha256:bcf3e58998965654fdaff38e58584d8937aa3096ab5354d493c77d1fdd66d7a1", size = 15101, upload-time = "2024-10-18T15:21:31.207Z" }, + { url = "https://files.pythonhosted.org/packages/29/88/07df22d2dd4df40aba9f3e402e6dc1b8ee86297dddbad4872bd5e7b0094f/MarkupSafe-3.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:e6a2a455bd412959b57a172ce6328d2dd1f01cb2135efda2e4576e8a23fa3b0f", size = 15603, upload-time = "2024-10-18T15:21:32.032Z" }, + { url = "https://files.pythonhosted.org/packages/62/6a/8b89d24db2d32d433dffcd6a8779159da109842434f1dd2f6e71f32f738c/MarkupSafe-3.0.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:b5a6b3ada725cea8a5e634536b1b01c30bcdcd7f9c6fff4151548d5bf6b3a36c", size = 14510, upload-time = "2024-10-18T15:21:33.625Z" }, + { url = "https://files.pythonhosted.org/packages/7a/06/a10f955f70a2e5a9bf78d11a161029d278eeacbd35ef806c3fd17b13060d/MarkupSafe-3.0.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a904af0a6162c73e3edcb969eeeb53a63ceeb5d8cf642fade7d39e7963a22ddb", size = 12486, upload-time = "2024-10-18T15:21:34.611Z" }, + { url = "https://files.pythonhosted.org/packages/34/cf/65d4a571869a1a9078198ca28f39fba5fbb910f952f9dbc5220afff9f5e6/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4aa4e5faecf353ed117801a068ebab7b7e09ffb6e1d5e412dc852e0da018126c", size = 25480, upload-time = "2024-10-18T15:21:35.398Z" }, + { url = "https://files.pythonhosted.org/packages/0c/e3/90e9651924c430b885468b56b3d597cabf6d72be4b24a0acd1fa0e12af67/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0ef13eaeee5b615fb07c9a7dadb38eac06a0608b41570d8ade51c56539e509d", size = 23914, upload-time = "2024-10-18T15:21:36.231Z" }, + { url = "https://files.pythonhosted.org/packages/66/8c/6c7cf61f95d63bb866db39085150df1f2a5bd3335298f14a66b48e92659c/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d16a81a06776313e817c951135cf7340a3e91e8c1ff2fac444cfd75fffa04afe", size = 23796, upload-time = "2024-10-18T15:21:37.073Z" }, + { url = "https://files.pythonhosted.org/packages/bb/35/cbe9238ec3f47ac9a7c8b3df7a808e7cb50fe149dc7039f5f454b3fba218/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:6381026f158fdb7c72a168278597a5e3a5222e83ea18f543112b2662a9b699c5", size = 25473, upload-time = "2024-10-18T15:21:37.932Z" }, + { url = "https://files.pythonhosted.org/packages/e6/32/7621a4382488aa283cc05e8984a9c219abad3bca087be9ec77e89939ded9/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:3d79d162e7be8f996986c064d1c7c817f6df3a77fe3d6859f6f9e7be4b8c213a", size = 24114, upload-time = "2024-10-18T15:21:39.799Z" }, + { url = "https://files.pythonhosted.org/packages/0d/80/0985960e4b89922cb5a0bac0ed39c5b96cbc1a536a99f30e8c220a996ed9/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:131a3c7689c85f5ad20f9f6fb1b866f402c445b220c19fe4308c0b147ccd2ad9", size = 24098, upload-time = "2024-10-18T15:21:40.813Z" }, + { url = "https://files.pythonhosted.org/packages/82/78/fedb03c7d5380df2427038ec8d973587e90561b2d90cd472ce9254cf348b/MarkupSafe-3.0.2-cp313-cp313t-win32.whl", hash = "sha256:ba8062ed2cf21c07a9e295d5b8a2a5ce678b913b45fdf68c32d95d6c1291e0b6", size = 15208, upload-time = "2024-10-18T15:21:41.814Z" }, + { url = "https://files.pythonhosted.org/packages/4f/65/6079a46068dfceaeabb5dcad6d674f5f5c61a6fa5673746f42a9f4c233b3/MarkupSafe-3.0.2-cp313-cp313t-win_amd64.whl", hash = "sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f", size = 15739, upload-time = "2024-10-18T15:21:42.784Z" }, +] + +[[package]] +name = "matplotlib" +version = "3.10.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "contourpy" }, + { name = "cycler" }, + { name = "fonttools" }, + { name = "kiwisolver" }, + { name = "numpy" }, + { name = "packaging" }, + { name = "pillow" }, + { name = "pyparsing" }, + { name = "python-dateutil" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/43/91/f2939bb60b7ebf12478b030e0d7f340247390f402b3b189616aad790c366/matplotlib-3.10.5.tar.gz", hash = "sha256:352ed6ccfb7998a00881692f38b4ca083c691d3e275b4145423704c34c909076", size = 34804044, upload-time = "2025-07-31T18:09:33.805Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/66/1e/c6f6bcd882d589410b475ca1fc22e34e34c82adff519caf18f3e6dd9d682/matplotlib-3.10.5-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:00b6feadc28a08bd3c65b2894f56cf3c94fc8f7adcbc6ab4516ae1e8ed8f62e2", size = 8253056, upload-time = "2025-07-31T18:08:05.385Z" }, + { url = "https://files.pythonhosted.org/packages/53/e6/d6f7d1b59413f233793dda14419776f5f443bcccb2dfc84b09f09fe05dbe/matplotlib-3.10.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ee98a5c5344dc7f48dc261b6ba5d9900c008fc12beb3fa6ebda81273602cc389", size = 8110131, upload-time = "2025-07-31T18:08:07.293Z" }, + { url = "https://files.pythonhosted.org/packages/66/2b/bed8a45e74957549197a2ac2e1259671cd80b55ed9e1fe2b5c94d88a9202/matplotlib-3.10.5-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:a17e57e33de901d221a07af32c08870ed4528db0b6059dce7d7e65c1122d4bea", size = 8669603, upload-time = "2025-07-31T18:08:09.064Z" }, + { url = "https://files.pythonhosted.org/packages/7e/a7/315e9435b10d057f5e52dfc603cd353167ae28bb1a4e033d41540c0067a4/matplotlib-3.10.5-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:97b9d6443419085950ee4a5b1ee08c363e5c43d7176e55513479e53669e88468", size = 9508127, upload-time = "2025-07-31T18:08:10.845Z" }, + { url = "https://files.pythonhosted.org/packages/7f/d9/edcbb1f02ca99165365d2768d517898c22c6040187e2ae2ce7294437c413/matplotlib-3.10.5-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ceefe5d40807d29a66ae916c6a3915d60ef9f028ce1927b84e727be91d884369", size = 9566926, upload-time = "2025-07-31T18:08:13.186Z" }, + { url = "https://files.pythonhosted.org/packages/3b/d9/6dd924ad5616c97b7308e6320cf392c466237a82a2040381163b7500510a/matplotlib-3.10.5-cp312-cp312-win_amd64.whl", hash = "sha256:c04cba0f93d40e45b3c187c6c52c17f24535b27d545f757a2fffebc06c12b98b", size = 8107599, upload-time = "2025-07-31T18:08:15.116Z" }, + { url = "https://files.pythonhosted.org/packages/0e/f3/522dc319a50f7b0279fbe74f86f7a3506ce414bc23172098e8d2bdf21894/matplotlib-3.10.5-cp312-cp312-win_arm64.whl", hash = "sha256:a41bcb6e2c8e79dc99c5511ae6f7787d2fb52efd3d805fff06d5d4f667db16b2", size = 7978173, upload-time = "2025-07-31T18:08:21.518Z" }, + { url = "https://files.pythonhosted.org/packages/8d/05/4f3c1f396075f108515e45cb8d334aff011a922350e502a7472e24c52d77/matplotlib-3.10.5-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:354204db3f7d5caaa10e5de74549ef6a05a4550fdd1c8f831ab9bca81efd39ed", size = 8253586, upload-time = "2025-07-31T18:08:23.107Z" }, + { url = "https://files.pythonhosted.org/packages/2f/2c/e084415775aac7016c3719fe7006cdb462582c6c99ac142f27303c56e243/matplotlib-3.10.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:b072aac0c3ad563a2b3318124756cb6112157017f7431626600ecbe890df57a1", size = 8110715, upload-time = "2025-07-31T18:08:24.675Z" }, + { url = "https://files.pythonhosted.org/packages/52/1b/233e3094b749df16e3e6cd5a44849fd33852e692ad009cf7de00cf58ddf6/matplotlib-3.10.5-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d52fd5b684d541b5a51fb276b2b97b010c75bee9aa392f96b4a07aeb491e33c7", size = 8669397, upload-time = "2025-07-31T18:08:26.778Z" }, + { url = "https://files.pythonhosted.org/packages/e8/ec/03f9e003a798f907d9f772eed9b7c6a9775d5bd00648b643ebfb88e25414/matplotlib-3.10.5-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee7a09ae2f4676276f5a65bd9f2bd91b4f9fbaedf49f40267ce3f9b448de501f", size = 9508646, upload-time = "2025-07-31T18:08:28.848Z" }, + { url = "https://files.pythonhosted.org/packages/91/e7/c051a7a386680c28487bca27d23b02d84f63e3d2a9b4d2fc478e6a42e37e/matplotlib-3.10.5-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ba6c3c9c067b83481d647af88b4e441d532acdb5ef22178a14935b0b881188f4", size = 9567424, upload-time = "2025-07-31T18:08:30.726Z" }, + { url = "https://files.pythonhosted.org/packages/36/c2/24302e93ff431b8f4173ee1dd88976c8d80483cadbc5d3d777cef47b3a1c/matplotlib-3.10.5-cp313-cp313-win_amd64.whl", hash = "sha256:07442d2692c9bd1cceaa4afb4bbe5b57b98a7599de4dabfcca92d3eea70f9ebe", size = 8107809, upload-time = "2025-07-31T18:08:33.928Z" }, + { url = "https://files.pythonhosted.org/packages/0b/33/423ec6a668d375dad825197557ed8fbdb74d62b432c1ed8235465945475f/matplotlib-3.10.5-cp313-cp313-win_arm64.whl", hash = "sha256:48fe6d47380b68a37ccfcc94f009530e84d41f71f5dae7eda7c4a5a84aa0a674", size = 7978078, upload-time = "2025-07-31T18:08:36.764Z" }, + { url = "https://files.pythonhosted.org/packages/51/17/521fc16ec766455c7bb52cc046550cf7652f6765ca8650ff120aa2d197b6/matplotlib-3.10.5-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:3b80eb8621331449fc519541a7461987f10afa4f9cfd91afcd2276ebe19bd56c", size = 8295590, upload-time = "2025-07-31T18:08:38.521Z" }, + { url = "https://files.pythonhosted.org/packages/f8/12/23c28b2c21114c63999bae129fce7fd34515641c517ae48ce7b7dcd33458/matplotlib-3.10.5-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:47a388908e469d6ca2a6015858fa924e0e8a2345a37125948d8e93a91c47933e", size = 8158518, upload-time = "2025-07-31T18:08:40.195Z" }, + { url = "https://files.pythonhosted.org/packages/81/f8/aae4eb25e8e7190759f3cb91cbeaa344128159ac92bb6b409e24f8711f78/matplotlib-3.10.5-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8b6b49167d208358983ce26e43aa4196073b4702858670f2eb111f9a10652b4b", size = 8691815, upload-time = "2025-07-31T18:08:42.238Z" }, + { url = "https://files.pythonhosted.org/packages/d0/ba/450c39ebdd486bd33a359fc17365ade46c6a96bf637bbb0df7824de2886c/matplotlib-3.10.5-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8a8da0453a7fd8e3da114234ba70c5ba9ef0e98f190309ddfde0f089accd46ea", size = 9522814, upload-time = "2025-07-31T18:08:44.914Z" }, + { url = "https://files.pythonhosted.org/packages/89/11/9c66f6a990e27bb9aa023f7988d2d5809cb98aa39c09cbf20fba75a542ef/matplotlib-3.10.5-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:52c6573dfcb7726a9907b482cd5b92e6b5499b284ffacb04ffbfe06b3e568124", size = 9573917, upload-time = "2025-07-31T18:08:47.038Z" }, + { url = "https://files.pythonhosted.org/packages/b3/69/8b49394de92569419e5e05e82e83df9b749a0ff550d07631ea96ed2eb35a/matplotlib-3.10.5-cp313-cp313t-win_amd64.whl", hash = "sha256:a23193db2e9d64ece69cac0c8231849db7dd77ce59c7b89948cf9d0ce655a3ce", size = 8181034, upload-time = "2025-07-31T18:08:48.943Z" }, + { url = "https://files.pythonhosted.org/packages/47/23/82dc435bb98a2fc5c20dffcac8f0b083935ac28286413ed8835df40d0baa/matplotlib-3.10.5-cp313-cp313t-win_arm64.whl", hash = "sha256:56da3b102cf6da2776fef3e71cd96fcf22103a13594a18ac9a9b31314e0be154", size = 8023337, upload-time = "2025-07-31T18:08:50.791Z" }, + { url = "https://files.pythonhosted.org/packages/ac/e0/26b6cfde31f5383503ee45dcb7e691d45dadf0b3f54639332b59316a97f8/matplotlib-3.10.5-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:96ef8f5a3696f20f55597ffa91c28e2e73088df25c555f8d4754931515512715", size = 8253591, upload-time = "2025-07-31T18:08:53.254Z" }, + { url = "https://files.pythonhosted.org/packages/c1/89/98488c7ef7ea20ea659af7499628c240a608b337af4be2066d644cfd0a0f/matplotlib-3.10.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:77fab633e94b9da60512d4fa0213daeb76d5a7b05156840c4fd0399b4b818837", size = 8112566, upload-time = "2025-07-31T18:08:55.116Z" }, + { url = "https://files.pythonhosted.org/packages/52/67/42294dfedc82aea55e1a767daf3263aacfb5a125f44ba189e685bab41b6f/matplotlib-3.10.5-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:27f52634315e96b1debbfdc5c416592edcd9c4221bc2f520fd39c33db5d9f202", size = 9513281, upload-time = "2025-07-31T18:08:56.885Z" }, + { url = "https://files.pythonhosted.org/packages/e7/68/f258239e0cf34c2cbc816781c7ab6fca768452e6bf1119aedd2bd4a882a3/matplotlib-3.10.5-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:525f6e28c485c769d1f07935b660c864de41c37fd716bfa64158ea646f7084bb", size = 9780873, upload-time = "2025-07-31T18:08:59.241Z" }, + { url = "https://files.pythonhosted.org/packages/89/64/f4881554006bd12e4558bd66778bdd15d47b00a1f6c6e8b50f6208eda4b3/matplotlib-3.10.5-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:1f5f3ec4c191253c5f2b7c07096a142c6a1c024d9f738247bfc8e3f9643fc975", size = 9568954, upload-time = "2025-07-31T18:09:01.244Z" }, + { url = "https://files.pythonhosted.org/packages/06/f8/42779d39c3f757e1f012f2dda3319a89fb602bd2ef98ce8faf0281f4febd/matplotlib-3.10.5-cp314-cp314-win_amd64.whl", hash = "sha256:707f9c292c4cd4716f19ab8a1f93f26598222cd931e0cd98fbbb1c5994bf7667", size = 8237465, upload-time = "2025-07-31T18:09:03.206Z" }, + { url = "https://files.pythonhosted.org/packages/cf/f8/153fd06b5160f0cd27c8b9dd797fcc9fb56ac6a0ebf3c1f765b6b68d3c8a/matplotlib-3.10.5-cp314-cp314-win_arm64.whl", hash = "sha256:21a95b9bf408178d372814de7baacd61c712a62cae560b5e6f35d791776f6516", size = 8108898, upload-time = "2025-07-31T18:09:05.231Z" }, + { url = "https://files.pythonhosted.org/packages/9a/ee/c4b082a382a225fe0d2a73f1f57cf6f6f132308805b493a54c8641006238/matplotlib-3.10.5-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:a6b310f95e1102a8c7c817ef17b60ee5d1851b8c71b63d9286b66b177963039e", size = 8295636, upload-time = "2025-07-31T18:09:07.306Z" }, + { url = "https://files.pythonhosted.org/packages/30/73/2195fa2099718b21a20da82dfc753bf2af58d596b51aefe93e359dd5915a/matplotlib-3.10.5-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:94986a242747a0605cb3ff1cb98691c736f28a59f8ffe5175acaeb7397c49a5a", size = 8158575, upload-time = "2025-07-31T18:09:09.083Z" }, + { url = "https://files.pythonhosted.org/packages/f6/e9/a08cdb34618a91fa08f75e6738541da5cacde7c307cea18ff10f0d03fcff/matplotlib-3.10.5-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1ff10ea43288f0c8bab608a305dc6c918cc729d429c31dcbbecde3b9f4d5b569", size = 9522815, upload-time = "2025-07-31T18:09:11.191Z" }, + { url = "https://files.pythonhosted.org/packages/4e/bb/34d8b7e0d1bb6d06ef45db01dfa560d5a67b1c40c0b998ce9ccde934bb09/matplotlib-3.10.5-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f6adb644c9d040ffb0d3434e440490a66cf73dbfa118a6f79cd7568431f7a012", size = 9783514, upload-time = "2025-07-31T18:09:13.307Z" }, + { url = "https://files.pythonhosted.org/packages/12/09/d330d1e55dcca2e11b4d304cc5227f52e2512e46828d6249b88e0694176e/matplotlib-3.10.5-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:4fa40a8f98428f789a9dcacd625f59b7bc4e3ef6c8c7c80187a7a709475cf592", size = 9573932, upload-time = "2025-07-31T18:09:15.335Z" }, + { url = "https://files.pythonhosted.org/packages/eb/3b/f70258ac729aa004aca673800a53a2b0a26d49ca1df2eaa03289a1c40f81/matplotlib-3.10.5-cp314-cp314t-win_amd64.whl", hash = "sha256:95672a5d628b44207aab91ec20bf59c26da99de12b88f7e0b1fb0a84a86ff959", size = 8322003, upload-time = "2025-07-31T18:09:17.416Z" }, + { url = "https://files.pythonhosted.org/packages/5b/60/3601f8ce6d76a7c81c7f25a0e15fde0d6b66226dd187aa6d2838e6374161/matplotlib-3.10.5-cp314-cp314t-win_arm64.whl", hash = "sha256:2efaf97d72629e74252e0b5e3c46813e9eeaa94e011ecf8084a971a31a97f40b", size = 8153849, upload-time = "2025-07-31T18:09:19.673Z" }, +] + +[[package]] +name = "mdurl" +version = "0.1.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", size = 8729, upload-time = "2022-08-14T12:40:10.846Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979, upload-time = "2022-08-14T12:40:09.779Z" }, +] + +[[package]] +name = "mpmath" +version = "1.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e0/47/dd32fa426cc72114383ac549964eecb20ecfd886d1e5ccf5340b55b02f57/mpmath-1.3.0.tar.gz", hash = "sha256:7a28eb2a9774d00c7bc92411c19a89209d5da7c4c9a9e227be8330a23a25b91f", size = 508106, upload-time = "2023-03-07T16:47:11.061Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/43/e3/7d92a15f894aa0c9c4b49b8ee9ac9850d6e63b03c9c32c0367a13ae62209/mpmath-1.3.0-py3-none-any.whl", hash = "sha256:a0b2b9fe80bbcd81a6647ff13108738cfb482d481d826cc0e02f5b35e5c88d2c", size = 536198, upload-time = "2023-03-07T16:47:09.197Z" }, +] + +[[package]] +name = "networkx" +version = "3.5" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6c/4f/ccdb8ad3a38e583f214547fd2f7ff1fc160c43a75af88e6aec213404b96a/networkx-3.5.tar.gz", hash = "sha256:d4c6f9cf81f52d69230866796b82afbccdec3db7ae4fbd1b65ea750feed50037", size = 2471065, upload-time = "2025-05-29T11:35:07.804Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/eb/8d/776adee7bbf76365fdd7f2552710282c79a4ead5d2a46408c9043a2b70ba/networkx-3.5-py3-none-any.whl", hash = "sha256:0030d386a9a06dee3565298b4a734b68589749a544acbb6c412dc9e2489ec6ec", size = 2034406, upload-time = "2025-05-29T11:35:04.961Z" }, +] + +[[package]] +name = "numpy" +version = "2.3.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/37/7d/3fec4199c5ffb892bed55cff901e4f39a58c81df9c44c280499e92cad264/numpy-2.3.2.tar.gz", hash = "sha256:e0486a11ec30cdecb53f184d496d1c6a20786c81e55e41640270130056f8ee48", size = 20489306, upload-time = "2025-07-24T21:32:07.553Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/00/6d/745dd1c1c5c284d17725e5c802ca4d45cfc6803519d777f087b71c9f4069/numpy-2.3.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:bc3186bea41fae9d8e90c2b4fb5f0a1f5a690682da79b92574d63f56b529080b", size = 20956420, upload-time = "2025-07-24T20:28:18.002Z" }, + { url = "https://files.pythonhosted.org/packages/bc/96/e7b533ea5740641dd62b07a790af5d9d8fec36000b8e2d0472bd7574105f/numpy-2.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2f4f0215edb189048a3c03bd5b19345bdfa7b45a7a6f72ae5945d2a28272727f", size = 14184660, upload-time = "2025-07-24T20:28:39.522Z" }, + { url = "https://files.pythonhosted.org/packages/2b/53/102c6122db45a62aa20d1b18c9986f67e6b97e0d6fbc1ae13e3e4c84430c/numpy-2.3.2-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:8b1224a734cd509f70816455c3cffe13a4f599b1bf7130f913ba0e2c0b2006c0", size = 5113382, upload-time = "2025-07-24T20:28:48.544Z" }, + { url = "https://files.pythonhosted.org/packages/2b/21/376257efcbf63e624250717e82b4fae93d60178f09eb03ed766dbb48ec9c/numpy-2.3.2-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:3dcf02866b977a38ba3ec10215220609ab9667378a9e2150615673f3ffd6c73b", size = 6647258, upload-time = "2025-07-24T20:28:59.104Z" }, + { url = "https://files.pythonhosted.org/packages/91/ba/f4ebf257f08affa464fe6036e13f2bf9d4642a40228781dc1235da81be9f/numpy-2.3.2-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:572d5512df5470f50ada8d1972c5f1082d9a0b7aa5944db8084077570cf98370", size = 14281409, upload-time = "2025-07-24T20:40:30.298Z" }, + { url = "https://files.pythonhosted.org/packages/59/ef/f96536f1df42c668cbacb727a8c6da7afc9c05ece6d558927fb1722693e1/numpy-2.3.2-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8145dd6d10df13c559d1e4314df29695613575183fa2e2d11fac4c208c8a1f73", size = 16641317, upload-time = "2025-07-24T20:40:56.625Z" }, + { url = "https://files.pythonhosted.org/packages/f6/a7/af813a7b4f9a42f498dde8a4c6fcbff8100eed00182cc91dbaf095645f38/numpy-2.3.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:103ea7063fa624af04a791c39f97070bf93b96d7af7eb23530cd087dc8dbe9dc", size = 16056262, upload-time = "2025-07-24T20:41:20.797Z" }, + { url = "https://files.pythonhosted.org/packages/8b/5d/41c4ef8404caaa7f05ed1cfb06afe16a25895260eacbd29b4d84dff2920b/numpy-2.3.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fc927d7f289d14f5e037be917539620603294454130b6de200091e23d27dc9be", size = 18579342, upload-time = "2025-07-24T20:41:50.753Z" }, + { url = "https://files.pythonhosted.org/packages/a1/4f/9950e44c5a11636f4a3af6e825ec23003475cc9a466edb7a759ed3ea63bd/numpy-2.3.2-cp312-cp312-win32.whl", hash = "sha256:d95f59afe7f808c103be692175008bab926b59309ade3e6d25009e9a171f7036", size = 6320610, upload-time = "2025-07-24T20:42:01.551Z" }, + { url = "https://files.pythonhosted.org/packages/7c/2f/244643a5ce54a94f0a9a2ab578189c061e4a87c002e037b0829dd77293b6/numpy-2.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:9e196ade2400c0c737d93465327d1ae7c06c7cb8a1756121ebf54b06ca183c7f", size = 12786292, upload-time = "2025-07-24T20:42:20.738Z" }, + { url = "https://files.pythonhosted.org/packages/54/cd/7b5f49d5d78db7badab22d8323c1b6ae458fbf86c4fdfa194ab3cd4eb39b/numpy-2.3.2-cp312-cp312-win_arm64.whl", hash = "sha256:ee807923782faaf60d0d7331f5e86da7d5e3079e28b291973c545476c2b00d07", size = 10194071, upload-time = "2025-07-24T20:42:36.657Z" }, + { url = "https://files.pythonhosted.org/packages/1c/c0/c6bb172c916b00700ed3bf71cb56175fd1f7dbecebf8353545d0b5519f6c/numpy-2.3.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:c8d9727f5316a256425892b043736d63e89ed15bbfe6556c5ff4d9d4448ff3b3", size = 20949074, upload-time = "2025-07-24T20:43:07.813Z" }, + { url = "https://files.pythonhosted.org/packages/20/4e/c116466d22acaf4573e58421c956c6076dc526e24a6be0903219775d862e/numpy-2.3.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:efc81393f25f14d11c9d161e46e6ee348637c0a1e8a54bf9dedc472a3fae993b", size = 14177311, upload-time = "2025-07-24T20:43:29.335Z" }, + { url = "https://files.pythonhosted.org/packages/78/45/d4698c182895af189c463fc91d70805d455a227261d950e4e0f1310c2550/numpy-2.3.2-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:dd937f088a2df683cbb79dda9a772b62a3e5a8a7e76690612c2737f38c6ef1b6", size = 5106022, upload-time = "2025-07-24T20:43:37.999Z" }, + { url = "https://files.pythonhosted.org/packages/9f/76/3e6880fef4420179309dba72a8c11f6166c431cf6dee54c577af8906f914/numpy-2.3.2-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:11e58218c0c46c80509186e460d79fbdc9ca1eb8d8aee39d8f2dc768eb781089", size = 6640135, upload-time = "2025-07-24T20:43:49.28Z" }, + { url = "https://files.pythonhosted.org/packages/34/fa/87ff7f25b3c4ce9085a62554460b7db686fef1e0207e8977795c7b7d7ba1/numpy-2.3.2-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5ad4ebcb683a1f99f4f392cc522ee20a18b2bb12a2c1c42c3d48d5a1adc9d3d2", size = 14278147, upload-time = "2025-07-24T20:44:10.328Z" }, + { url = "https://files.pythonhosted.org/packages/1d/0f/571b2c7a3833ae419fe69ff7b479a78d313581785203cc70a8db90121b9a/numpy-2.3.2-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:938065908d1d869c7d75d8ec45f735a034771c6ea07088867f713d1cd3bbbe4f", size = 16635989, upload-time = "2025-07-24T20:44:34.88Z" }, + { url = "https://files.pythonhosted.org/packages/24/5a/84ae8dca9c9a4c592fe11340b36a86ffa9fd3e40513198daf8a97839345c/numpy-2.3.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:66459dccc65d8ec98cc7df61307b64bf9e08101f9598755d42d8ae65d9a7a6ee", size = 16053052, upload-time = "2025-07-24T20:44:58.872Z" }, + { url = "https://files.pythonhosted.org/packages/57/7c/e5725d99a9133b9813fcf148d3f858df98511686e853169dbaf63aec6097/numpy-2.3.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a7af9ed2aa9ec5950daf05bb11abc4076a108bd3c7db9aa7251d5f107079b6a6", size = 18577955, upload-time = "2025-07-24T20:45:26.714Z" }, + { url = "https://files.pythonhosted.org/packages/ae/11/7c546fcf42145f29b71e4d6f429e96d8d68e5a7ba1830b2e68d7418f0bbd/numpy-2.3.2-cp313-cp313-win32.whl", hash = "sha256:906a30249315f9c8e17b085cc5f87d3f369b35fedd0051d4a84686967bdbbd0b", size = 6311843, upload-time = "2025-07-24T20:49:24.444Z" }, + { url = "https://files.pythonhosted.org/packages/aa/6f/a428fd1cb7ed39b4280d057720fed5121b0d7754fd2a9768640160f5517b/numpy-2.3.2-cp313-cp313-win_amd64.whl", hash = "sha256:c63d95dc9d67b676e9108fe0d2182987ccb0f11933c1e8959f42fa0da8d4fa56", size = 12782876, upload-time = "2025-07-24T20:49:43.227Z" }, + { url = "https://files.pythonhosted.org/packages/65/85/4ea455c9040a12595fb6c43f2c217257c7b52dd0ba332c6a6c1d28b289fe/numpy-2.3.2-cp313-cp313-win_arm64.whl", hash = "sha256:b05a89f2fb84d21235f93de47129dd4f11c16f64c87c33f5e284e6a3a54e43f2", size = 10192786, upload-time = "2025-07-24T20:49:59.443Z" }, + { url = "https://files.pythonhosted.org/packages/80/23/8278f40282d10c3f258ec3ff1b103d4994bcad78b0cba9208317f6bb73da/numpy-2.3.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:4e6ecfeddfa83b02318f4d84acf15fbdbf9ded18e46989a15a8b6995dfbf85ab", size = 21047395, upload-time = "2025-07-24T20:45:58.821Z" }, + { url = "https://files.pythonhosted.org/packages/1f/2d/624f2ce4a5df52628b4ccd16a4f9437b37c35f4f8a50d00e962aae6efd7a/numpy-2.3.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:508b0eada3eded10a3b55725b40806a4b855961040180028f52580c4729916a2", size = 14300374, upload-time = "2025-07-24T20:46:20.207Z" }, + { url = "https://files.pythonhosted.org/packages/f6/62/ff1e512cdbb829b80a6bd08318a58698867bca0ca2499d101b4af063ee97/numpy-2.3.2-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:754d6755d9a7588bdc6ac47dc4ee97867271b17cee39cb87aef079574366db0a", size = 5228864, upload-time = "2025-07-24T20:46:30.58Z" }, + { url = "https://files.pythonhosted.org/packages/7d/8e/74bc18078fff03192d4032cfa99d5a5ca937807136d6f5790ce07ca53515/numpy-2.3.2-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:a9f66e7d2b2d7712410d3bc5684149040ef5f19856f20277cd17ea83e5006286", size = 6737533, upload-time = "2025-07-24T20:46:46.111Z" }, + { url = "https://files.pythonhosted.org/packages/19/ea/0731efe2c9073ccca5698ef6a8c3667c4cf4eea53fcdcd0b50140aba03bc/numpy-2.3.2-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:de6ea4e5a65d5a90c7d286ddff2b87f3f4ad61faa3db8dabe936b34c2275b6f8", size = 14352007, upload-time = "2025-07-24T20:47:07.1Z" }, + { url = "https://files.pythonhosted.org/packages/cf/90/36be0865f16dfed20f4bc7f75235b963d5939707d4b591f086777412ff7b/numpy-2.3.2-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a3ef07ec8cbc8fc9e369c8dcd52019510c12da4de81367d8b20bc692aa07573a", size = 16701914, upload-time = "2025-07-24T20:47:32.459Z" }, + { url = "https://files.pythonhosted.org/packages/94/30/06cd055e24cb6c38e5989a9e747042b4e723535758e6153f11afea88c01b/numpy-2.3.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:27c9f90e7481275c7800dc9c24b7cc40ace3fdb970ae4d21eaff983a32f70c91", size = 16132708, upload-time = "2025-07-24T20:47:58.129Z" }, + { url = "https://files.pythonhosted.org/packages/9a/14/ecede608ea73e58267fd7cb78f42341b3b37ba576e778a1a06baffbe585c/numpy-2.3.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:07b62978075b67eee4065b166d000d457c82a1efe726cce608b9db9dd66a73a5", size = 18651678, upload-time = "2025-07-24T20:48:25.402Z" }, + { url = "https://files.pythonhosted.org/packages/40/f3/2fe6066b8d07c3685509bc24d56386534c008b462a488b7f503ba82b8923/numpy-2.3.2-cp313-cp313t-win32.whl", hash = "sha256:c771cfac34a4f2c0de8e8c97312d07d64fd8f8ed45bc9f5726a7e947270152b5", size = 6441832, upload-time = "2025-07-24T20:48:37.181Z" }, + { url = "https://files.pythonhosted.org/packages/0b/ba/0937d66d05204d8f28630c9c60bc3eda68824abde4cf756c4d6aad03b0c6/numpy-2.3.2-cp313-cp313t-win_amd64.whl", hash = "sha256:72dbebb2dcc8305c431b2836bcc66af967df91be793d63a24e3d9b741374c450", size = 12927049, upload-time = "2025-07-24T20:48:56.24Z" }, + { url = "https://files.pythonhosted.org/packages/e9/ed/13542dd59c104d5e654dfa2ac282c199ba64846a74c2c4bcdbc3a0f75df1/numpy-2.3.2-cp313-cp313t-win_arm64.whl", hash = "sha256:72c6df2267e926a6d5286b0a6d556ebe49eae261062059317837fda12ddf0c1a", size = 10262935, upload-time = "2025-07-24T20:49:13.136Z" }, + { url = "https://files.pythonhosted.org/packages/c9/7c/7659048aaf498f7611b783e000c7268fcc4dcf0ce21cd10aad7b2e8f9591/numpy-2.3.2-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:448a66d052d0cf14ce9865d159bfc403282c9bc7bb2a31b03cc18b651eca8b1a", size = 20950906, upload-time = "2025-07-24T20:50:30.346Z" }, + { url = "https://files.pythonhosted.org/packages/80/db/984bea9d4ddf7112a04cfdfb22b1050af5757864cfffe8e09e44b7f11a10/numpy-2.3.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:546aaf78e81b4081b2eba1d105c3b34064783027a06b3ab20b6eba21fb64132b", size = 14185607, upload-time = "2025-07-24T20:50:51.923Z" }, + { url = "https://files.pythonhosted.org/packages/e4/76/b3d6f414f4eca568f469ac112a3b510938d892bc5a6c190cb883af080b77/numpy-2.3.2-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:87c930d52f45df092f7578889711a0768094debf73cfcde105e2d66954358125", size = 5114110, upload-time = "2025-07-24T20:51:01.041Z" }, + { url = "https://files.pythonhosted.org/packages/9e/d2/6f5e6826abd6bca52392ed88fe44a4b52aacb60567ac3bc86c67834c3a56/numpy-2.3.2-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:8dc082ea901a62edb8f59713c6a7e28a85daddcb67454c839de57656478f5b19", size = 6642050, upload-time = "2025-07-24T20:51:11.64Z" }, + { url = "https://files.pythonhosted.org/packages/c4/43/f12b2ade99199e39c73ad182f103f9d9791f48d885c600c8e05927865baf/numpy-2.3.2-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:af58de8745f7fa9ca1c0c7c943616c6fe28e75d0c81f5c295810e3c83b5be92f", size = 14296292, upload-time = "2025-07-24T20:51:33.488Z" }, + { url = "https://files.pythonhosted.org/packages/5d/f9/77c07d94bf110a916b17210fac38680ed8734c236bfed9982fd8524a7b47/numpy-2.3.2-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fed5527c4cf10f16c6d0b6bee1f89958bccb0ad2522c8cadc2efd318bcd545f5", size = 16638913, upload-time = "2025-07-24T20:51:58.517Z" }, + { url = "https://files.pythonhosted.org/packages/9b/d1/9d9f2c8ea399cc05cfff8a7437453bd4e7d894373a93cdc46361bbb49a7d/numpy-2.3.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:095737ed986e00393ec18ec0b21b47c22889ae4b0cd2d5e88342e08b01141f58", size = 16071180, upload-time = "2025-07-24T20:52:22.827Z" }, + { url = "https://files.pythonhosted.org/packages/4c/41/82e2c68aff2a0c9bf315e47d61951099fed65d8cb2c8d9dc388cb87e947e/numpy-2.3.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:b5e40e80299607f597e1a8a247ff8d71d79c5b52baa11cc1cce30aa92d2da6e0", size = 18576809, upload-time = "2025-07-24T20:52:51.015Z" }, + { url = "https://files.pythonhosted.org/packages/14/14/4b4fd3efb0837ed252d0f583c5c35a75121038a8c4e065f2c259be06d2d8/numpy-2.3.2-cp314-cp314-win32.whl", hash = "sha256:7d6e390423cc1f76e1b8108c9b6889d20a7a1f59d9a60cac4a050fa734d6c1e2", size = 6366410, upload-time = "2025-07-24T20:56:44.949Z" }, + { url = "https://files.pythonhosted.org/packages/11/9e/b4c24a6b8467b61aced5c8dc7dcfce23621baa2e17f661edb2444a418040/numpy-2.3.2-cp314-cp314-win_amd64.whl", hash = "sha256:b9d0878b21e3918d76d2209c924ebb272340da1fb51abc00f986c258cd5e957b", size = 12918821, upload-time = "2025-07-24T20:57:06.479Z" }, + { url = "https://files.pythonhosted.org/packages/0e/0f/0dc44007c70b1007c1cef86b06986a3812dd7106d8f946c09cfa75782556/numpy-2.3.2-cp314-cp314-win_arm64.whl", hash = "sha256:2738534837c6a1d0c39340a190177d7d66fdf432894f469728da901f8f6dc910", size = 10477303, upload-time = "2025-07-24T20:57:22.879Z" }, + { url = "https://files.pythonhosted.org/packages/8b/3e/075752b79140b78ddfc9c0a1634d234cfdbc6f9bbbfa6b7504e445ad7d19/numpy-2.3.2-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:4d002ecf7c9b53240be3bb69d80f86ddbd34078bae04d87be81c1f58466f264e", size = 21047524, upload-time = "2025-07-24T20:53:22.086Z" }, + { url = "https://files.pythonhosted.org/packages/fe/6d/60e8247564a72426570d0e0ea1151b95ce5bd2f1597bb878a18d32aec855/numpy-2.3.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:293b2192c6bcce487dbc6326de5853787f870aeb6c43f8f9c6496db5b1781e45", size = 14300519, upload-time = "2025-07-24T20:53:44.053Z" }, + { url = "https://files.pythonhosted.org/packages/4d/73/d8326c442cd428d47a067070c3ac6cc3b651a6e53613a1668342a12d4479/numpy-2.3.2-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:0a4f2021a6da53a0d580d6ef5db29947025ae8b35b3250141805ea9a32bbe86b", size = 5228972, upload-time = "2025-07-24T20:53:53.81Z" }, + { url = "https://files.pythonhosted.org/packages/34/2e/e71b2d6dad075271e7079db776196829019b90ce3ece5c69639e4f6fdc44/numpy-2.3.2-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:9c144440db4bf3bb6372d2c3e49834cc0ff7bb4c24975ab33e01199e645416f2", size = 6737439, upload-time = "2025-07-24T20:54:04.742Z" }, + { url = "https://files.pythonhosted.org/packages/15/b0/d004bcd56c2c5e0500ffc65385eb6d569ffd3363cb5e593ae742749b2daa/numpy-2.3.2-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f92d6c2a8535dc4fe4419562294ff957f83a16ebdec66df0805e473ffaad8bd0", size = 14352479, upload-time = "2025-07-24T20:54:25.819Z" }, + { url = "https://files.pythonhosted.org/packages/11/e3/285142fcff8721e0c99b51686426165059874c150ea9ab898e12a492e291/numpy-2.3.2-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cefc2219baa48e468e3db7e706305fcd0c095534a192a08f31e98d83a7d45fb0", size = 16702805, upload-time = "2025-07-24T20:54:50.814Z" }, + { url = "https://files.pythonhosted.org/packages/33/c3/33b56b0e47e604af2c7cd065edca892d180f5899599b76830652875249a3/numpy-2.3.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:76c3e9501ceb50b2ff3824c3589d5d1ab4ac857b0ee3f8f49629d0de55ecf7c2", size = 16133830, upload-time = "2025-07-24T20:55:17.306Z" }, + { url = "https://files.pythonhosted.org/packages/6e/ae/7b1476a1f4d6a48bc669b8deb09939c56dd2a439db1ab03017844374fb67/numpy-2.3.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:122bf5ed9a0221b3419672493878ba4967121514b1d7d4656a7580cd11dddcbf", size = 18652665, upload-time = "2025-07-24T20:55:46.665Z" }, + { url = "https://files.pythonhosted.org/packages/14/ba/5b5c9978c4bb161034148ade2de9db44ec316fab89ce8c400db0e0c81f86/numpy-2.3.2-cp314-cp314t-win32.whl", hash = "sha256:6f1ae3dcb840edccc45af496f312528c15b1f79ac318169d094e85e4bb35fdf1", size = 6514777, upload-time = "2025-07-24T20:55:57.66Z" }, + { url = "https://files.pythonhosted.org/packages/eb/46/3dbaf0ae7c17cdc46b9f662c56da2054887b8d9e737c1476f335c83d33db/numpy-2.3.2-cp314-cp314t-win_amd64.whl", hash = "sha256:087ffc25890d89a43536f75c5fe8770922008758e8eeeef61733957041ed2f9b", size = 13111856, upload-time = "2025-07-24T20:56:17.318Z" }, + { url = "https://files.pythonhosted.org/packages/c1/9e/1652778bce745a67b5fe05adde60ed362d38eb17d919a540e813d30f6874/numpy-2.3.2-cp314-cp314t-win_arm64.whl", hash = "sha256:092aeb3449833ea9c0bf0089d70c29ae480685dd2377ec9cdbbb620257f84631", size = 10544226, upload-time = "2025-07-24T20:56:34.509Z" }, +] + +[[package]] +name = "nvidia-cublas-cu12" +version = "12.8.4.1" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/dc/61/e24b560ab2e2eaeb3c839129175fb330dfcfc29e5203196e5541a4c44682/nvidia_cublas_cu12-12.8.4.1-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:8ac4e771d5a348c551b2a426eda6193c19aa630236b418086020df5ba9667142", size = 594346921, upload-time = "2025-03-07T01:44:31.254Z" }, +] + +[[package]] +name = "nvidia-cuda-cupti-cu12" +version = "12.8.90" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f8/02/2adcaa145158bf1a8295d83591d22e4103dbfd821bcaf6f3f53151ca4ffa/nvidia_cuda_cupti_cu12-12.8.90-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ea0cb07ebda26bb9b29ba82cda34849e73c166c18162d3913575b0c9db9a6182", size = 10248621, upload-time = "2025-03-07T01:40:21.213Z" }, +] + +[[package]] +name = "nvidia-cuda-nvrtc-cu12" +version = "12.8.93" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/05/6b/32f747947df2da6994e999492ab306a903659555dddc0fbdeb9d71f75e52/nvidia_cuda_nvrtc_cu12-12.8.93-py3-none-manylinux2010_x86_64.manylinux_2_12_x86_64.whl", hash = "sha256:a7756528852ef889772a84c6cd89d41dfa74667e24cca16bb31f8f061e3e9994", size = 88040029, upload-time = "2025-03-07T01:42:13.562Z" }, +] + +[[package]] +name = "nvidia-cuda-runtime-cu12" +version = "12.8.90" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0d/9b/a997b638fcd068ad6e4d53b8551a7d30fe8b404d6f1804abf1df69838932/nvidia_cuda_runtime_cu12-12.8.90-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:adade8dcbd0edf427b7204d480d6066d33902cab2a4707dcfc48a2d0fd44ab90", size = 954765, upload-time = "2025-03-07T01:40:01.615Z" }, +] + +[[package]] +name = "nvidia-cudnn-cu12" +version = "9.10.2.21" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "nvidia-cublas-cu12", marker = "(platform_machine != 'aarch64' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != 'linux' and sys_platform != 'win32')" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/ba/51/e123d997aa098c61d029f76663dedbfb9bc8dcf8c60cbd6adbe42f76d049/nvidia_cudnn_cu12-9.10.2.21-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:949452be657fa16687d0930933f032835951ef0892b37d2d53824d1a84dc97a8", size = 706758467, upload-time = "2025-06-06T21:54:08.597Z" }, +] + +[[package]] +name = "nvidia-cufft-cu12" +version = "11.3.3.83" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "nvidia-nvjitlink-cu12", marker = "(platform_machine != 'aarch64' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != 'linux' and sys_platform != 'win32')" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/1f/13/ee4e00f30e676b66ae65b4f08cb5bcbb8392c03f54f2d5413ea99a5d1c80/nvidia_cufft_cu12-11.3.3.83-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:4d2dd21ec0b88cf61b62e6b43564355e5222e4a3fb394cac0db101f2dd0d4f74", size = 193118695, upload-time = "2025-03-07T01:45:27.821Z" }, +] + +[[package]] +name = "nvidia-cufile-cu12" +version = "1.13.1.3" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bb/fe/1bcba1dfbfb8d01be8d93f07bfc502c93fa23afa6fd5ab3fc7c1df71038a/nvidia_cufile_cu12-1.13.1.3-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1d069003be650e131b21c932ec3d8969c1715379251f8d23a1860554b1cb24fc", size = 1197834, upload-time = "2025-03-07T01:45:50.723Z" }, +] + +[[package]] +name = "nvidia-curand-cu12" +version = "10.3.9.90" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fb/aa/6584b56dc84ebe9cf93226a5cde4d99080c8e90ab40f0c27bda7a0f29aa1/nvidia_curand_cu12-10.3.9.90-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:b32331d4f4df5d6eefa0554c565b626c7216f87a06a4f56fab27c3b68a830ec9", size = 63619976, upload-time = "2025-03-07T01:46:23.323Z" }, +] + +[[package]] +name = "nvidia-cusolver-cu12" +version = "11.7.3.90" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "nvidia-cublas-cu12", marker = "(platform_machine != 'aarch64' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != 'linux' and sys_platform != 'win32')" }, + { name = "nvidia-cusparse-cu12", marker = "(platform_machine != 'aarch64' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != 'linux' and sys_platform != 'win32')" }, + { name = "nvidia-nvjitlink-cu12", marker = "(platform_machine != 'aarch64' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != 'linux' and sys_platform != 'win32')" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/85/48/9a13d2975803e8cf2777d5ed57b87a0b6ca2cc795f9a4f59796a910bfb80/nvidia_cusolver_cu12-11.7.3.90-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:4376c11ad263152bd50ea295c05370360776f8c3427b30991df774f9fb26c450", size = 267506905, upload-time = "2025-03-07T01:47:16.273Z" }, +] + +[[package]] +name = "nvidia-cusparse-cu12" +version = "12.5.8.93" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "nvidia-nvjitlink-cu12", marker = "(platform_machine != 'aarch64' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != 'linux' and sys_platform != 'win32')" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/c2/f5/e1854cb2f2bcd4280c44736c93550cc300ff4b8c95ebe370d0aa7d2b473d/nvidia_cusparse_cu12-12.5.8.93-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1ec05d76bbbd8b61b06a80e1eaf8cf4959c3d4ce8e711b65ebd0443bb0ebb13b", size = 288216466, upload-time = "2025-03-07T01:48:13.779Z" }, +] + +[[package]] +name = "nvidia-cusparselt-cu12" +version = "0.7.1" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/56/79/12978b96bd44274fe38b5dde5cfb660b1d114f70a65ef962bcbbed99b549/nvidia_cusparselt_cu12-0.7.1-py3-none-manylinux2014_x86_64.whl", hash = "sha256:f1bb701d6b930d5a7cea44c19ceb973311500847f81b634d802b7b539dc55623", size = 287193691, upload-time = "2025-02-26T00:15:44.104Z" }, +] + +[[package]] +name = "nvidia-nccl-cu12" +version = "2.27.3" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5c/5b/4e4fff7bad39adf89f735f2bc87248c81db71205b62bcc0d5ca5b606b3c3/nvidia_nccl_cu12-2.27.3-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:adf27ccf4238253e0b826bce3ff5fa532d65fc42322c8bfdfaf28024c0fbe039", size = 322364134, upload-time = "2025-06-03T21:58:04.013Z" }, +] + +[[package]] +name = "nvidia-nvjitlink-cu12" +version = "12.8.93" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f6/74/86a07f1d0f42998ca31312f998bd3b9a7eff7f52378f4f270c8679c77fb9/nvidia_nvjitlink_cu12-12.8.93-py3-none-manylinux2010_x86_64.manylinux_2_12_x86_64.whl", hash = "sha256:81ff63371a7ebd6e6451970684f916be2eab07321b73c9d244dc2b4da7f73b88", size = 39254836, upload-time = "2025-03-07T01:49:55.661Z" }, +] + +[[package]] +name = "nvidia-nvtx-cu12" +version = "12.8.90" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a2/eb/86626c1bbc2edb86323022371c39aa48df6fd8b0a1647bc274577f72e90b/nvidia_nvtx_cu12-12.8.90-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:5b17e2001cc0d751a5bc2c6ec6d26ad95913324a4adb86788c944f8ce9ba441f", size = 89954, upload-time = "2025-03-07T01:42:44.131Z" }, +] + +[[package]] +name = "opencv-python" +version = "4.11.0.86" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/17/06/68c27a523103dad5837dc5b87e71285280c4f098c60e4fe8a8db6486ab09/opencv-python-4.11.0.86.tar.gz", hash = "sha256:03d60ccae62304860d232272e4a4fda93c39d595780cb40b161b310244b736a4", size = 95171956, upload-time = "2025-01-16T13:52:24.737Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/05/4d/53b30a2a3ac1f75f65a59eb29cf2ee7207ce64867db47036ad61743d5a23/opencv_python-4.11.0.86-cp37-abi3-macosx_13_0_arm64.whl", hash = "sha256:432f67c223f1dc2824f5e73cdfcd9db0efc8710647d4e813012195dc9122a52a", size = 37326322, upload-time = "2025-01-16T13:52:25.887Z" }, + { url = "https://files.pythonhosted.org/packages/3b/84/0a67490741867eacdfa37bc18df96e08a9d579583b419010d7f3da8ff503/opencv_python-4.11.0.86-cp37-abi3-macosx_13_0_x86_64.whl", hash = "sha256:9d05ef13d23fe97f575153558653e2d6e87103995d54e6a35db3f282fe1f9c66", size = 56723197, upload-time = "2025-01-16T13:55:21.222Z" }, + { url = "https://files.pythonhosted.org/packages/f3/bd/29c126788da65c1fb2b5fb621b7fed0ed5f9122aa22a0868c5e2c15c6d23/opencv_python-4.11.0.86-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1b92ae2c8852208817e6776ba1ea0d6b1e0a1b5431e971a2a0ddd2a8cc398202", size = 42230439, upload-time = "2025-01-16T13:51:35.822Z" }, + { url = "https://files.pythonhosted.org/packages/2c/8b/90eb44a40476fa0e71e05a0283947cfd74a5d36121a11d926ad6f3193cc4/opencv_python-4.11.0.86-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6b02611523803495003bd87362db3e1d2a0454a6a63025dc6658a9830570aa0d", size = 62986597, upload-time = "2025-01-16T13:52:08.836Z" }, + { url = "https://files.pythonhosted.org/packages/fb/d7/1d5941a9dde095468b288d989ff6539dd69cd429dbf1b9e839013d21b6f0/opencv_python-4.11.0.86-cp37-abi3-win32.whl", hash = "sha256:810549cb2a4aedaa84ad9a1c92fbfdfc14090e2749cedf2c1589ad8359aa169b", size = 29384337, upload-time = "2025-01-16T13:52:13.549Z" }, + { url = "https://files.pythonhosted.org/packages/a4/7d/f1c30a92854540bf789e9cd5dde7ef49bbe63f855b85a2e6b3db8135c591/opencv_python-4.11.0.86-cp37-abi3-win_amd64.whl", hash = "sha256:085ad9b77c18853ea66283e98affefe2de8cc4c1f43eda4c100cf9b2721142ec", size = 39488044, upload-time = "2025-01-16T13:52:21.928Z" }, +] + +[[package]] +name = "packaging" +version = "25.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a1/d4/1fc4078c65507b51b96ca8f8c3ba19e6a61c8253c72794544580a7b6c24d/packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f", size = 165727, upload-time = "2025-04-19T11:48:59.673Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469, upload-time = "2025-04-19T11:48:57.875Z" }, +] + +[[package]] +name = "pandas" +version = "2.3.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, + { name = "python-dateutil" }, + { name = "pytz" }, + { name = "tzdata" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d1/6f/75aa71f8a14267117adeeed5d21b204770189c0a0025acbdc03c337b28fc/pandas-2.3.1.tar.gz", hash = "sha256:0a95b9ac964fe83ce317827f80304d37388ea77616b1425f0ae41c9d2d0d7bb2", size = 4487493, upload-time = "2025-07-07T19:20:04.079Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/46/de/b8445e0f5d217a99fe0eeb2f4988070908979bec3587c0633e5428ab596c/pandas-2.3.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:689968e841136f9e542020698ee1c4fbe9caa2ed2213ae2388dc7b81721510d3", size = 11588172, upload-time = "2025-07-07T19:18:52.054Z" }, + { url = "https://files.pythonhosted.org/packages/1e/e0/801cdb3564e65a5ac041ab99ea6f1d802a6c325bb6e58c79c06a3f1cd010/pandas-2.3.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:025e92411c16cbe5bb2a4abc99732a6b132f439b8aab23a59fa593eb00704232", size = 10717365, upload-time = "2025-07-07T19:18:54.785Z" }, + { url = "https://files.pythonhosted.org/packages/51/a5/c76a8311833c24ae61a376dbf360eb1b1c9247a5d9c1e8b356563b31b80c/pandas-2.3.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9b7ff55f31c4fcb3e316e8f7fa194566b286d6ac430afec0d461163312c5841e", size = 11280411, upload-time = "2025-07-07T19:18:57.045Z" }, + { url = "https://files.pythonhosted.org/packages/da/01/e383018feba0a1ead6cf5fe8728e5d767fee02f06a3d800e82c489e5daaf/pandas-2.3.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7dcb79bf373a47d2a40cf7232928eb7540155abbc460925c2c96d2d30b006eb4", size = 11988013, upload-time = "2025-07-07T19:18:59.771Z" }, + { url = "https://files.pythonhosted.org/packages/5b/14/cec7760d7c9507f11c97d64f29022e12a6cc4fc03ac694535e89f88ad2ec/pandas-2.3.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:56a342b231e8862c96bdb6ab97170e203ce511f4d0429589c8ede1ee8ece48b8", size = 12767210, upload-time = "2025-07-07T19:19:02.944Z" }, + { url = "https://files.pythonhosted.org/packages/50/b9/6e2d2c6728ed29fb3d4d4d302504fb66f1a543e37eb2e43f352a86365cdf/pandas-2.3.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ca7ed14832bce68baef331f4d7f294411bed8efd032f8109d690df45e00c4679", size = 13440571, upload-time = "2025-07-07T19:19:06.82Z" }, + { url = "https://files.pythonhosted.org/packages/80/a5/3a92893e7399a691bad7664d977cb5e7c81cf666c81f89ea76ba2bff483d/pandas-2.3.1-cp312-cp312-win_amd64.whl", hash = "sha256:ac942bfd0aca577bef61f2bc8da8147c4ef6879965ef883d8e8d5d2dc3e744b8", size = 10987601, upload-time = "2025-07-07T19:19:09.589Z" }, + { url = "https://files.pythonhosted.org/packages/32/ed/ff0a67a2c5505e1854e6715586ac6693dd860fbf52ef9f81edee200266e7/pandas-2.3.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:9026bd4a80108fac2239294a15ef9003c4ee191a0f64b90f170b40cfb7cf2d22", size = 11531393, upload-time = "2025-07-07T19:19:12.245Z" }, + { url = "https://files.pythonhosted.org/packages/c7/db/d8f24a7cc9fb0972adab0cc80b6817e8bef888cfd0024eeb5a21c0bb5c4a/pandas-2.3.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:6de8547d4fdb12421e2d047a2c446c623ff4c11f47fddb6b9169eb98ffba485a", size = 10668750, upload-time = "2025-07-07T19:19:14.612Z" }, + { url = "https://files.pythonhosted.org/packages/0f/b0/80f6ec783313f1e2356b28b4fd8d2148c378370045da918c73145e6aab50/pandas-2.3.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:782647ddc63c83133b2506912cc6b108140a38a37292102aaa19c81c83db2928", size = 11342004, upload-time = "2025-07-07T19:19:16.857Z" }, + { url = "https://files.pythonhosted.org/packages/e9/e2/20a317688435470872885e7fc8f95109ae9683dec7c50be29b56911515a5/pandas-2.3.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2ba6aff74075311fc88504b1db890187a3cd0f887a5b10f5525f8e2ef55bfdb9", size = 12050869, upload-time = "2025-07-07T19:19:19.265Z" }, + { url = "https://files.pythonhosted.org/packages/55/79/20d746b0a96c67203a5bee5fb4e00ac49c3e8009a39e1f78de264ecc5729/pandas-2.3.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e5635178b387bd2ba4ac040f82bc2ef6e6b500483975c4ebacd34bec945fda12", size = 12750218, upload-time = "2025-07-07T19:19:21.547Z" }, + { url = "https://files.pythonhosted.org/packages/7c/0f/145c8b41e48dbf03dd18fdd7f24f8ba95b8254a97a3379048378f33e7838/pandas-2.3.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6f3bf5ec947526106399a9e1d26d40ee2b259c66422efdf4de63c848492d91bb", size = 13416763, upload-time = "2025-07-07T19:19:23.939Z" }, + { url = "https://files.pythonhosted.org/packages/b2/c0/54415af59db5cdd86a3d3bf79863e8cc3fa9ed265f0745254061ac09d5f2/pandas-2.3.1-cp313-cp313-win_amd64.whl", hash = "sha256:1c78cf43c8fde236342a1cb2c34bcff89564a7bfed7e474ed2fffa6aed03a956", size = 10987482, upload-time = "2025-07-07T19:19:42.699Z" }, + { url = "https://files.pythonhosted.org/packages/48/64/2fd2e400073a1230e13b8cd604c9bc95d9e3b962e5d44088ead2e8f0cfec/pandas-2.3.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:8dfc17328e8da77be3cf9f47509e5637ba8f137148ed0e9b5241e1baf526e20a", size = 12029159, upload-time = "2025-07-07T19:19:26.362Z" }, + { url = "https://files.pythonhosted.org/packages/d8/0a/d84fd79b0293b7ef88c760d7dca69828d867c89b6d9bc52d6a27e4d87316/pandas-2.3.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:ec6c851509364c59a5344458ab935e6451b31b818be467eb24b0fe89bd05b6b9", size = 11393287, upload-time = "2025-07-07T19:19:29.157Z" }, + { url = "https://files.pythonhosted.org/packages/50/ae/ff885d2b6e88f3c7520bb74ba319268b42f05d7e583b5dded9837da2723f/pandas-2.3.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:911580460fc4884d9b05254b38a6bfadddfcc6aaef856fb5859e7ca202e45275", size = 11309381, upload-time = "2025-07-07T19:19:31.436Z" }, + { url = "https://files.pythonhosted.org/packages/85/86/1fa345fc17caf5d7780d2699985c03dbe186c68fee00b526813939062bb0/pandas-2.3.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2f4d6feeba91744872a600e6edbbd5b033005b431d5ae8379abee5bcfa479fab", size = 11883998, upload-time = "2025-07-07T19:19:34.267Z" }, + { url = "https://files.pythonhosted.org/packages/81/aa/e58541a49b5e6310d89474333e994ee57fea97c8aaa8fc7f00b873059bbf/pandas-2.3.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:fe37e757f462d31a9cd7580236a82f353f5713a80e059a29753cf938c6775d96", size = 12704705, upload-time = "2025-07-07T19:19:36.856Z" }, + { url = "https://files.pythonhosted.org/packages/d5/f9/07086f5b0f2a19872554abeea7658200824f5835c58a106fa8f2ae96a46c/pandas-2.3.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:5db9637dbc24b631ff3707269ae4559bce4b7fd75c1c4d7e13f40edc42df4444", size = 13189044, upload-time = "2025-07-07T19:19:39.999Z" }, +] + +[[package]] +name = "pillow" +version = "11.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f3/0d/d0d6dea55cd152ce3d6767bb38a8fc10e33796ba4ba210cbab9354b6d238/pillow-11.3.0.tar.gz", hash = "sha256:3828ee7586cd0b2091b6209e5ad53e20d0649bbe87164a459d0676e035e8f523", size = 47113069, upload-time = "2025-07-01T09:16:30.666Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/40/fe/1bc9b3ee13f68487a99ac9529968035cca2f0a51ec36892060edcc51d06a/pillow-11.3.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:fdae223722da47b024b867c1ea0be64e0df702c5e0a60e27daad39bf960dd1e4", size = 5278800, upload-time = "2025-07-01T09:14:17.648Z" }, + { url = "https://files.pythonhosted.org/packages/2c/32/7e2ac19b5713657384cec55f89065fb306b06af008cfd87e572035b27119/pillow-11.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:921bd305b10e82b4d1f5e802b6850677f965d8394203d182f078873851dada69", size = 4686296, upload-time = "2025-07-01T09:14:19.828Z" }, + { url = "https://files.pythonhosted.org/packages/8e/1e/b9e12bbe6e4c2220effebc09ea0923a07a6da1e1f1bfbc8d7d29a01ce32b/pillow-11.3.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:eb76541cba2f958032d79d143b98a3a6b3ea87f0959bbe256c0b5e416599fd5d", size = 5871726, upload-time = "2025-07-03T13:10:04.448Z" }, + { url = "https://files.pythonhosted.org/packages/8d/33/e9200d2bd7ba00dc3ddb78df1198a6e80d7669cce6c2bdbeb2530a74ec58/pillow-11.3.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:67172f2944ebba3d4a7b54f2e95c786a3a50c21b88456329314caaa28cda70f6", size = 7644652, upload-time = "2025-07-03T13:10:10.391Z" }, + { url = "https://files.pythonhosted.org/packages/41/f1/6f2427a26fc683e00d985bc391bdd76d8dd4e92fac33d841127eb8fb2313/pillow-11.3.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:97f07ed9f56a3b9b5f49d3661dc9607484e85c67e27f3e8be2c7d28ca032fec7", size = 5977787, upload-time = "2025-07-01T09:14:21.63Z" }, + { url = "https://files.pythonhosted.org/packages/e4/c9/06dd4a38974e24f932ff5f98ea3c546ce3f8c995d3f0985f8e5ba48bba19/pillow-11.3.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:676b2815362456b5b3216b4fd5bd89d362100dc6f4945154ff172e206a22c024", size = 6645236, upload-time = "2025-07-01T09:14:23.321Z" }, + { url = "https://files.pythonhosted.org/packages/40/e7/848f69fb79843b3d91241bad658e9c14f39a32f71a301bcd1d139416d1be/pillow-11.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3e184b2f26ff146363dd07bde8b711833d7b0202e27d13540bfe2e35a323a809", size = 6086950, upload-time = "2025-07-01T09:14:25.237Z" }, + { url = "https://files.pythonhosted.org/packages/0b/1a/7cff92e695a2a29ac1958c2a0fe4c0b2393b60aac13b04a4fe2735cad52d/pillow-11.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:6be31e3fc9a621e071bc17bb7de63b85cbe0bfae91bb0363c893cbe67247780d", size = 6723358, upload-time = "2025-07-01T09:14:27.053Z" }, + { url = "https://files.pythonhosted.org/packages/26/7d/73699ad77895f69edff76b0f332acc3d497f22f5d75e5360f78cbcaff248/pillow-11.3.0-cp312-cp312-win32.whl", hash = "sha256:7b161756381f0918e05e7cb8a371fff367e807770f8fe92ecb20d905d0e1c149", size = 6275079, upload-time = "2025-07-01T09:14:30.104Z" }, + { url = "https://files.pythonhosted.org/packages/8c/ce/e7dfc873bdd9828f3b6e5c2bbb74e47a98ec23cc5c74fc4e54462f0d9204/pillow-11.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:a6444696fce635783440b7f7a9fc24b3ad10a9ea3f0ab66c5905be1c19ccf17d", size = 6986324, upload-time = "2025-07-01T09:14:31.899Z" }, + { url = "https://files.pythonhosted.org/packages/16/8f/b13447d1bf0b1f7467ce7d86f6e6edf66c0ad7cf44cf5c87a37f9bed9936/pillow-11.3.0-cp312-cp312-win_arm64.whl", hash = "sha256:2aceea54f957dd4448264f9bf40875da0415c83eb85f55069d89c0ed436e3542", size = 2423067, upload-time = "2025-07-01T09:14:33.709Z" }, + { url = "https://files.pythonhosted.org/packages/1e/93/0952f2ed8db3a5a4c7a11f91965d6184ebc8cd7cbb7941a260d5f018cd2d/pillow-11.3.0-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:1c627742b539bba4309df89171356fcb3cc5a9178355b2727d1b74a6cf155fbd", size = 2128328, upload-time = "2025-07-01T09:14:35.276Z" }, + { url = "https://files.pythonhosted.org/packages/4b/e8/100c3d114b1a0bf4042f27e0f87d2f25e857e838034e98ca98fe7b8c0a9c/pillow-11.3.0-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:30b7c02f3899d10f13d7a48163c8969e4e653f8b43416d23d13d1bbfdc93b9f8", size = 2170652, upload-time = "2025-07-01T09:14:37.203Z" }, + { url = "https://files.pythonhosted.org/packages/aa/86/3f758a28a6e381758545f7cdb4942e1cb79abd271bea932998fc0db93cb6/pillow-11.3.0-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:7859a4cc7c9295f5838015d8cc0a9c215b77e43d07a25e460f35cf516df8626f", size = 2227443, upload-time = "2025-07-01T09:14:39.344Z" }, + { url = "https://files.pythonhosted.org/packages/01/f4/91d5b3ffa718df2f53b0dc109877993e511f4fd055d7e9508682e8aba092/pillow-11.3.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ec1ee50470b0d050984394423d96325b744d55c701a439d2bd66089bff963d3c", size = 5278474, upload-time = "2025-07-01T09:14:41.843Z" }, + { url = "https://files.pythonhosted.org/packages/f9/0e/37d7d3eca6c879fbd9dba21268427dffda1ab00d4eb05b32923d4fbe3b12/pillow-11.3.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7db51d222548ccfd274e4572fdbf3e810a5e66b00608862f947b163e613b67dd", size = 4686038, upload-time = "2025-07-01T09:14:44.008Z" }, + { url = "https://files.pythonhosted.org/packages/ff/b0/3426e5c7f6565e752d81221af9d3676fdbb4f352317ceafd42899aaf5d8a/pillow-11.3.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:2d6fcc902a24ac74495df63faad1884282239265c6839a0a6416d33faedfae7e", size = 5864407, upload-time = "2025-07-03T13:10:15.628Z" }, + { url = "https://files.pythonhosted.org/packages/fc/c1/c6c423134229f2a221ee53f838d4be9d82bab86f7e2f8e75e47b6bf6cd77/pillow-11.3.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f0f5d8f4a08090c6d6d578351a2b91acf519a54986c055af27e7a93feae6d3f1", size = 7639094, upload-time = "2025-07-03T13:10:21.857Z" }, + { url = "https://files.pythonhosted.org/packages/ba/c9/09e6746630fe6372c67c648ff9deae52a2bc20897d51fa293571977ceb5d/pillow-11.3.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c37d8ba9411d6003bba9e518db0db0c58a680ab9fe5179f040b0463644bc9805", size = 5973503, upload-time = "2025-07-01T09:14:45.698Z" }, + { url = "https://files.pythonhosted.org/packages/d5/1c/a2a29649c0b1983d3ef57ee87a66487fdeb45132df66ab30dd37f7dbe162/pillow-11.3.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:13f87d581e71d9189ab21fe0efb5a23e9f28552d5be6979e84001d3b8505abe8", size = 6642574, upload-time = "2025-07-01T09:14:47.415Z" }, + { url = "https://files.pythonhosted.org/packages/36/de/d5cc31cc4b055b6c6fd990e3e7f0f8aaf36229a2698501bcb0cdf67c7146/pillow-11.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:023f6d2d11784a465f09fd09a34b150ea4672e85fb3d05931d89f373ab14abb2", size = 6084060, upload-time = "2025-07-01T09:14:49.636Z" }, + { url = "https://files.pythonhosted.org/packages/d5/ea/502d938cbaeec836ac28a9b730193716f0114c41325db428e6b280513f09/pillow-11.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:45dfc51ac5975b938e9809451c51734124e73b04d0f0ac621649821a63852e7b", size = 6721407, upload-time = "2025-07-01T09:14:51.962Z" }, + { url = "https://files.pythonhosted.org/packages/45/9c/9c5e2a73f125f6cbc59cc7087c8f2d649a7ae453f83bd0362ff7c9e2aee2/pillow-11.3.0-cp313-cp313-win32.whl", hash = "sha256:a4d336baed65d50d37b88ca5b60c0fa9d81e3a87d4a7930d3880d1624d5b31f3", size = 6273841, upload-time = "2025-07-01T09:14:54.142Z" }, + { url = "https://files.pythonhosted.org/packages/23/85/397c73524e0cd212067e0c969aa245b01d50183439550d24d9f55781b776/pillow-11.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:0bce5c4fd0921f99d2e858dc4d4d64193407e1b99478bc5cacecba2311abde51", size = 6978450, upload-time = "2025-07-01T09:14:56.436Z" }, + { url = "https://files.pythonhosted.org/packages/17/d2/622f4547f69cd173955194b78e4d19ca4935a1b0f03a302d655c9f6aae65/pillow-11.3.0-cp313-cp313-win_arm64.whl", hash = "sha256:1904e1264881f682f02b7f8167935cce37bc97db457f8e7849dc3a6a52b99580", size = 2423055, upload-time = "2025-07-01T09:14:58.072Z" }, + { url = "https://files.pythonhosted.org/packages/dd/80/a8a2ac21dda2e82480852978416cfacd439a4b490a501a288ecf4fe2532d/pillow-11.3.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:4c834a3921375c48ee6b9624061076bc0a32a60b5532b322cc0ea64e639dd50e", size = 5281110, upload-time = "2025-07-01T09:14:59.79Z" }, + { url = "https://files.pythonhosted.org/packages/44/d6/b79754ca790f315918732e18f82a8146d33bcd7f4494380457ea89eb883d/pillow-11.3.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:5e05688ccef30ea69b9317a9ead994b93975104a677a36a8ed8106be9260aa6d", size = 4689547, upload-time = "2025-07-01T09:15:01.648Z" }, + { url = "https://files.pythonhosted.org/packages/49/20/716b8717d331150cb00f7fdd78169c01e8e0c219732a78b0e59b6bdb2fd6/pillow-11.3.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:1019b04af07fc0163e2810167918cb5add8d74674b6267616021ab558dc98ced", size = 5901554, upload-time = "2025-07-03T13:10:27.018Z" }, + { url = "https://files.pythonhosted.org/packages/74/cf/a9f3a2514a65bb071075063a96f0a5cf949c2f2fce683c15ccc83b1c1cab/pillow-11.3.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f944255db153ebb2b19c51fe85dd99ef0ce494123f21b9db4877ffdfc5590c7c", size = 7669132, upload-time = "2025-07-03T13:10:33.01Z" }, + { url = "https://files.pythonhosted.org/packages/98/3c/da78805cbdbee9cb43efe8261dd7cc0b4b93f2ac79b676c03159e9db2187/pillow-11.3.0-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1f85acb69adf2aaee8b7da124efebbdb959a104db34d3a2cb0f3793dbae422a8", size = 6005001, upload-time = "2025-07-01T09:15:03.365Z" }, + { url = "https://files.pythonhosted.org/packages/6c/fa/ce044b91faecf30e635321351bba32bab5a7e034c60187fe9698191aef4f/pillow-11.3.0-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:05f6ecbeff5005399bb48d198f098a9b4b6bdf27b8487c7f38ca16eeb070cd59", size = 6668814, upload-time = "2025-07-01T09:15:05.655Z" }, + { url = "https://files.pythonhosted.org/packages/7b/51/90f9291406d09bf93686434f9183aba27b831c10c87746ff49f127ee80cb/pillow-11.3.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:a7bc6e6fd0395bc052f16b1a8670859964dbd7003bd0af2ff08342eb6e442cfe", size = 6113124, upload-time = "2025-07-01T09:15:07.358Z" }, + { url = "https://files.pythonhosted.org/packages/cd/5a/6fec59b1dfb619234f7636d4157d11fb4e196caeee220232a8d2ec48488d/pillow-11.3.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:83e1b0161c9d148125083a35c1c5a89db5b7054834fd4387499e06552035236c", size = 6747186, upload-time = "2025-07-01T09:15:09.317Z" }, + { url = "https://files.pythonhosted.org/packages/49/6b/00187a044f98255225f172de653941e61da37104a9ea60e4f6887717e2b5/pillow-11.3.0-cp313-cp313t-win32.whl", hash = "sha256:2a3117c06b8fb646639dce83694f2f9eac405472713fcb1ae887469c0d4f6788", size = 6277546, upload-time = "2025-07-01T09:15:11.311Z" }, + { url = "https://files.pythonhosted.org/packages/e8/5c/6caaba7e261c0d75bab23be79f1d06b5ad2a2ae49f028ccec801b0e853d6/pillow-11.3.0-cp313-cp313t-win_amd64.whl", hash = "sha256:857844335c95bea93fb39e0fa2726b4d9d758850b34075a7e3ff4f4fa3aa3b31", size = 6985102, upload-time = "2025-07-01T09:15:13.164Z" }, + { url = "https://files.pythonhosted.org/packages/f3/7e/b623008460c09a0cb38263c93b828c666493caee2eb34ff67f778b87e58c/pillow-11.3.0-cp313-cp313t-win_arm64.whl", hash = "sha256:8797edc41f3e8536ae4b10897ee2f637235c94f27404cac7297f7b607dd0716e", size = 2424803, upload-time = "2025-07-01T09:15:15.695Z" }, + { url = "https://files.pythonhosted.org/packages/73/f4/04905af42837292ed86cb1b1dabe03dce1edc008ef14c473c5c7e1443c5d/pillow-11.3.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:d9da3df5f9ea2a89b81bb6087177fb1f4d1c7146d583a3fe5c672c0d94e55e12", size = 5278520, upload-time = "2025-07-01T09:15:17.429Z" }, + { url = "https://files.pythonhosted.org/packages/41/b0/33d79e377a336247df6348a54e6d2a2b85d644ca202555e3faa0cf811ecc/pillow-11.3.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:0b275ff9b04df7b640c59ec5a3cb113eefd3795a8df80bac69646ef699c6981a", size = 4686116, upload-time = "2025-07-01T09:15:19.423Z" }, + { url = "https://files.pythonhosted.org/packages/49/2d/ed8bc0ab219ae8768f529597d9509d184fe8a6c4741a6864fea334d25f3f/pillow-11.3.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:0743841cabd3dba6a83f38a92672cccbd69af56e3e91777b0ee7f4dba4385632", size = 5864597, upload-time = "2025-07-03T13:10:38.404Z" }, + { url = "https://files.pythonhosted.org/packages/b5/3d/b932bb4225c80b58dfadaca9d42d08d0b7064d2d1791b6a237f87f661834/pillow-11.3.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:2465a69cf967b8b49ee1b96d76718cd98c4e925414ead59fdf75cf0fd07df673", size = 7638246, upload-time = "2025-07-03T13:10:44.987Z" }, + { url = "https://files.pythonhosted.org/packages/09/b5/0487044b7c096f1b48f0d7ad416472c02e0e4bf6919541b111efd3cae690/pillow-11.3.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:41742638139424703b4d01665b807c6468e23e699e8e90cffefe291c5832b027", size = 5973336, upload-time = "2025-07-01T09:15:21.237Z" }, + { url = "https://files.pythonhosted.org/packages/a8/2d/524f9318f6cbfcc79fbc004801ea6b607ec3f843977652fdee4857a7568b/pillow-11.3.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:93efb0b4de7e340d99057415c749175e24c8864302369e05914682ba642e5d77", size = 6642699, upload-time = "2025-07-01T09:15:23.186Z" }, + { url = "https://files.pythonhosted.org/packages/6f/d2/a9a4f280c6aefedce1e8f615baaa5474e0701d86dd6f1dede66726462bbd/pillow-11.3.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7966e38dcd0fa11ca390aed7c6f20454443581d758242023cf36fcb319b1a874", size = 6083789, upload-time = "2025-07-01T09:15:25.1Z" }, + { url = "https://files.pythonhosted.org/packages/fe/54/86b0cd9dbb683a9d5e960b66c7379e821a19be4ac5810e2e5a715c09a0c0/pillow-11.3.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:98a9afa7b9007c67ed84c57c9e0ad86a6000da96eaa638e4f8abe5b65ff83f0a", size = 6720386, upload-time = "2025-07-01T09:15:27.378Z" }, + { url = "https://files.pythonhosted.org/packages/e7/95/88efcaf384c3588e24259c4203b909cbe3e3c2d887af9e938c2022c9dd48/pillow-11.3.0-cp314-cp314-win32.whl", hash = "sha256:02a723e6bf909e7cea0dac1b0e0310be9d7650cd66222a5f1c571455c0a45214", size = 6370911, upload-time = "2025-07-01T09:15:29.294Z" }, + { url = "https://files.pythonhosted.org/packages/2e/cc/934e5820850ec5eb107e7b1a72dd278140731c669f396110ebc326f2a503/pillow-11.3.0-cp314-cp314-win_amd64.whl", hash = "sha256:a418486160228f64dd9e9efcd132679b7a02a5f22c982c78b6fc7dab3fefb635", size = 7117383, upload-time = "2025-07-01T09:15:31.128Z" }, + { url = "https://files.pythonhosted.org/packages/d6/e9/9c0a616a71da2a5d163aa37405e8aced9a906d574b4a214bede134e731bc/pillow-11.3.0-cp314-cp314-win_arm64.whl", hash = "sha256:155658efb5e044669c08896c0c44231c5e9abcaadbc5cd3648df2f7c0b96b9a6", size = 2511385, upload-time = "2025-07-01T09:15:33.328Z" }, + { url = "https://files.pythonhosted.org/packages/1a/33/c88376898aff369658b225262cd4f2659b13e8178e7534df9e6e1fa289f6/pillow-11.3.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:59a03cdf019efbfeeed910bf79c7c93255c3d54bc45898ac2a4140071b02b4ae", size = 5281129, upload-time = "2025-07-01T09:15:35.194Z" }, + { url = "https://files.pythonhosted.org/packages/1f/70/d376247fb36f1844b42910911c83a02d5544ebd2a8bad9efcc0f707ea774/pillow-11.3.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:f8a5827f84d973d8636e9dc5764af4f0cf2318d26744b3d902931701b0d46653", size = 4689580, upload-time = "2025-07-01T09:15:37.114Z" }, + { url = "https://files.pythonhosted.org/packages/eb/1c/537e930496149fbac69efd2fc4329035bbe2e5475b4165439e3be9cb183b/pillow-11.3.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ee92f2fd10f4adc4b43d07ec5e779932b4eb3dbfbc34790ada5a6669bc095aa6", size = 5902860, upload-time = "2025-07-03T13:10:50.248Z" }, + { url = "https://files.pythonhosted.org/packages/bd/57/80f53264954dcefeebcf9dae6e3eb1daea1b488f0be8b8fef12f79a3eb10/pillow-11.3.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c96d333dcf42d01f47b37e0979b6bd73ec91eae18614864622d9b87bbd5bbf36", size = 7670694, upload-time = "2025-07-03T13:10:56.432Z" }, + { url = "https://files.pythonhosted.org/packages/70/ff/4727d3b71a8578b4587d9c276e90efad2d6fe0335fd76742a6da08132e8c/pillow-11.3.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4c96f993ab8c98460cd0c001447bff6194403e8b1d7e149ade5f00594918128b", size = 6005888, upload-time = "2025-07-01T09:15:39.436Z" }, + { url = "https://files.pythonhosted.org/packages/05/ae/716592277934f85d3be51d7256f3636672d7b1abfafdc42cf3f8cbd4b4c8/pillow-11.3.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:41342b64afeba938edb034d122b2dda5db2139b9a4af999729ba8818e0056477", size = 6670330, upload-time = "2025-07-01T09:15:41.269Z" }, + { url = "https://files.pythonhosted.org/packages/e7/bb/7fe6cddcc8827b01b1a9766f5fdeb7418680744f9082035bdbabecf1d57f/pillow-11.3.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:068d9c39a2d1b358eb9f245ce7ab1b5c3246c7c8c7d9ba58cfa5b43146c06e50", size = 6114089, upload-time = "2025-07-01T09:15:43.13Z" }, + { url = "https://files.pythonhosted.org/packages/8b/f5/06bfaa444c8e80f1a8e4bff98da9c83b37b5be3b1deaa43d27a0db37ef84/pillow-11.3.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:a1bc6ba083b145187f648b667e05a2534ecc4b9f2784c2cbe3089e44868f2b9b", size = 6748206, upload-time = "2025-07-01T09:15:44.937Z" }, + { url = "https://files.pythonhosted.org/packages/f0/77/bc6f92a3e8e6e46c0ca78abfffec0037845800ea38c73483760362804c41/pillow-11.3.0-cp314-cp314t-win32.whl", hash = "sha256:118ca10c0d60b06d006be10a501fd6bbdfef559251ed31b794668ed569c87e12", size = 6377370, upload-time = "2025-07-01T09:15:46.673Z" }, + { url = "https://files.pythonhosted.org/packages/4a/82/3a721f7d69dca802befb8af08b7c79ebcab461007ce1c18bd91a5d5896f9/pillow-11.3.0-cp314-cp314t-win_amd64.whl", hash = "sha256:8924748b688aa210d79883357d102cd64690e56b923a186f35a82cbc10f997db", size = 7121500, upload-time = "2025-07-01T09:15:48.512Z" }, + { url = "https://files.pythonhosted.org/packages/89/c7/5572fa4a3f45740eaab6ae86fcdf7195b55beac1371ac8c619d880cfe948/pillow-11.3.0-cp314-cp314t-win_arm64.whl", hash = "sha256:79ea0d14d3ebad43ec77ad5272e6ff9bba5b679ef73375ea760261207fa8e0aa", size = 2512835, upload-time = "2025-07-01T09:15:50.399Z" }, +] + +[[package]] +name = "psutil" +version = "7.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/2a/80/336820c1ad9286a4ded7e845b2eccfcb27851ab8ac6abece774a6ff4d3de/psutil-7.0.0.tar.gz", hash = "sha256:7be9c3eba38beccb6495ea33afd982a44074b78f28c434a1f51cc07fd315c456", size = 497003, upload-time = "2025-02-13T21:54:07.946Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ed/e6/2d26234410f8b8abdbf891c9da62bee396583f713fb9f3325a4760875d22/psutil-7.0.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:101d71dc322e3cffd7cea0650b09b3d08b8e7c4109dd6809fe452dfd00e58b25", size = 238051, upload-time = "2025-02-13T21:54:12.36Z" }, + { url = "https://files.pythonhosted.org/packages/04/8b/30f930733afe425e3cbfc0e1468a30a18942350c1a8816acfade80c005c4/psutil-7.0.0-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:39db632f6bb862eeccf56660871433e111b6ea58f2caea825571951d4b6aa3da", size = 239535, upload-time = "2025-02-13T21:54:16.07Z" }, + { url = "https://files.pythonhosted.org/packages/2a/ed/d362e84620dd22876b55389248e522338ed1bf134a5edd3b8231d7207f6d/psutil-7.0.0-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1fcee592b4c6f146991ca55919ea3d1f8926497a713ed7faaf8225e174581e91", size = 275004, upload-time = "2025-02-13T21:54:18.662Z" }, + { url = "https://files.pythonhosted.org/packages/bf/b9/b0eb3f3cbcb734d930fdf839431606844a825b23eaf9a6ab371edac8162c/psutil-7.0.0-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4b1388a4f6875d7e2aff5c4ca1cc16c545ed41dd8bb596cefea80111db353a34", size = 277986, upload-time = "2025-02-13T21:54:21.811Z" }, + { url = "https://files.pythonhosted.org/packages/eb/a2/709e0fe2f093556c17fbafda93ac032257242cabcc7ff3369e2cb76a97aa/psutil-7.0.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5f098451abc2828f7dc6b58d44b532b22f2088f4999a937557b603ce72b1993", size = 279544, upload-time = "2025-02-13T21:54:24.68Z" }, + { url = "https://files.pythonhosted.org/packages/50/e6/eecf58810b9d12e6427369784efe814a1eec0f492084ce8eb8f4d89d6d61/psutil-7.0.0-cp37-abi3-win32.whl", hash = "sha256:ba3fcef7523064a6c9da440fc4d6bd07da93ac726b5733c29027d7dc95b39d99", size = 241053, upload-time = "2025-02-13T21:54:34.31Z" }, + { url = "https://files.pythonhosted.org/packages/50/1b/6921afe68c74868b4c9fa424dad3be35b095e16687989ebbb50ce4fceb7c/psutil-7.0.0-cp37-abi3-win_amd64.whl", hash = "sha256:4cf3d4eb1aa9b348dec30105c55cd9b7d4629285735a102beb4441e38db90553", size = 244885, upload-time = "2025-02-13T21:54:37.486Z" }, +] + +[[package]] +name = "py-cpuinfo" +version = "9.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/37/a8/d832f7293ebb21690860d2e01d8115e5ff6f2ae8bbdc953f0eb0fa4bd2c7/py-cpuinfo-9.0.0.tar.gz", hash = "sha256:3cdbbf3fac90dc6f118bfd64384f309edeadd902d7c8fb17f02ffa1fc3f49690", size = 104716, upload-time = "2022-10-25T20:38:06.303Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e0/a9/023730ba63db1e494a271cb018dcd361bd2c917ba7004c3e49d5daf795a2/py_cpuinfo-9.0.0-py3-none-any.whl", hash = "sha256:859625bc251f64e21f077d099d4162689c762b5d6a4c3c97553d56241c9674d5", size = 22335, upload-time = "2022-10-25T20:38:27.636Z" }, +] + +[[package]] +name = "pydantic" +version = "2.11.7" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "annotated-types" }, + { name = "pydantic-core" }, + { name = "typing-extensions" }, + { name = "typing-inspection" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/00/dd/4325abf92c39ba8623b5af936ddb36ffcfe0beae70405d456ab1fb2f5b8c/pydantic-2.11.7.tar.gz", hash = "sha256:d989c3c6cb79469287b1569f7447a17848c998458d49ebe294e975b9baf0f0db", size = 788350, upload-time = "2025-06-14T08:33:17.137Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6a/c0/ec2b1c8712ca690e5d61979dee872603e92b8a32f94cc1b72d53beab008a/pydantic-2.11.7-py3-none-any.whl", hash = "sha256:dde5df002701f6de26248661f6835bbe296a47bf73990135c7d07ce741b9623b", size = 444782, upload-time = "2025-06-14T08:33:14.905Z" }, +] + +[[package]] +name = "pydantic-core" +version = "2.33.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ad/88/5f2260bdfae97aabf98f1778d43f69574390ad787afb646292a638c923d4/pydantic_core-2.33.2.tar.gz", hash = "sha256:7cb8bc3605c29176e1b105350d2e6474142d7c1bd1d9327c4a9bdb46bf827acc", size = 435195, upload-time = "2025-04-23T18:33:52.104Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/18/8a/2b41c97f554ec8c71f2a8a5f85cb56a8b0956addfe8b0efb5b3d77e8bdc3/pydantic_core-2.33.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:a7ec89dc587667f22b6a0b6579c249fca9026ce7c333fc142ba42411fa243cdc", size = 2009000, upload-time = "2025-04-23T18:31:25.863Z" }, + { url = "https://files.pythonhosted.org/packages/a1/02/6224312aacb3c8ecbaa959897af57181fb6cf3a3d7917fd44d0f2917e6f2/pydantic_core-2.33.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3c6db6e52c6d70aa0d00d45cdb9b40f0433b96380071ea80b09277dba021ddf7", size = 1847996, upload-time = "2025-04-23T18:31:27.341Z" }, + { url = "https://files.pythonhosted.org/packages/d6/46/6dcdf084a523dbe0a0be59d054734b86a981726f221f4562aed313dbcb49/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e61206137cbc65e6d5256e1166f88331d3b6238e082d9f74613b9b765fb9025", size = 1880957, upload-time = "2025-04-23T18:31:28.956Z" }, + { url = "https://files.pythonhosted.org/packages/ec/6b/1ec2c03837ac00886ba8160ce041ce4e325b41d06a034adbef11339ae422/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:eb8c529b2819c37140eb51b914153063d27ed88e3bdc31b71198a198e921e011", size = 1964199, upload-time = "2025-04-23T18:31:31.025Z" }, + { url = "https://files.pythonhosted.org/packages/2d/1d/6bf34d6adb9debd9136bd197ca72642203ce9aaaa85cfcbfcf20f9696e83/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c52b02ad8b4e2cf14ca7b3d918f3eb0ee91e63b3167c32591e57c4317e134f8f", size = 2120296, upload-time = "2025-04-23T18:31:32.514Z" }, + { url = "https://files.pythonhosted.org/packages/e0/94/2bd0aaf5a591e974b32a9f7123f16637776c304471a0ab33cf263cf5591a/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:96081f1605125ba0855dfda83f6f3df5ec90c61195421ba72223de35ccfb2f88", size = 2676109, upload-time = "2025-04-23T18:31:33.958Z" }, + { url = "https://files.pythonhosted.org/packages/f9/41/4b043778cf9c4285d59742281a769eac371b9e47e35f98ad321349cc5d61/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f57a69461af2a5fa6e6bbd7a5f60d3b7e6cebb687f55106933188e79ad155c1", size = 2002028, upload-time = "2025-04-23T18:31:39.095Z" }, + { url = "https://files.pythonhosted.org/packages/cb/d5/7bb781bf2748ce3d03af04d5c969fa1308880e1dca35a9bd94e1a96a922e/pydantic_core-2.33.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:572c7e6c8bb4774d2ac88929e3d1f12bc45714ae5ee6d9a788a9fb35e60bb04b", size = 2100044, upload-time = "2025-04-23T18:31:41.034Z" }, + { url = "https://files.pythonhosted.org/packages/fe/36/def5e53e1eb0ad896785702a5bbfd25eed546cdcf4087ad285021a90ed53/pydantic_core-2.33.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:db4b41f9bd95fbe5acd76d89920336ba96f03e149097365afe1cb092fceb89a1", size = 2058881, upload-time = "2025-04-23T18:31:42.757Z" }, + { url = "https://files.pythonhosted.org/packages/01/6c/57f8d70b2ee57fc3dc8b9610315949837fa8c11d86927b9bb044f8705419/pydantic_core-2.33.2-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:fa854f5cf7e33842a892e5c73f45327760bc7bc516339fda888c75ae60edaeb6", size = 2227034, upload-time = "2025-04-23T18:31:44.304Z" }, + { url = "https://files.pythonhosted.org/packages/27/b9/9c17f0396a82b3d5cbea4c24d742083422639e7bb1d5bf600e12cb176a13/pydantic_core-2.33.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:5f483cfb75ff703095c59e365360cb73e00185e01aaea067cd19acffd2ab20ea", size = 2234187, upload-time = "2025-04-23T18:31:45.891Z" }, + { url = "https://files.pythonhosted.org/packages/b0/6a/adf5734ffd52bf86d865093ad70b2ce543415e0e356f6cacabbc0d9ad910/pydantic_core-2.33.2-cp312-cp312-win32.whl", hash = "sha256:9cb1da0f5a471435a7bc7e439b8a728e8b61e59784b2af70d7c169f8dd8ae290", size = 1892628, upload-time = "2025-04-23T18:31:47.819Z" }, + { url = "https://files.pythonhosted.org/packages/43/e4/5479fecb3606c1368d496a825d8411e126133c41224c1e7238be58b87d7e/pydantic_core-2.33.2-cp312-cp312-win_amd64.whl", hash = "sha256:f941635f2a3d96b2973e867144fde513665c87f13fe0e193c158ac51bfaaa7b2", size = 1955866, upload-time = "2025-04-23T18:31:49.635Z" }, + { url = "https://files.pythonhosted.org/packages/0d/24/8b11e8b3e2be9dd82df4b11408a67c61bb4dc4f8e11b5b0fc888b38118b5/pydantic_core-2.33.2-cp312-cp312-win_arm64.whl", hash = "sha256:cca3868ddfaccfbc4bfb1d608e2ccaaebe0ae628e1416aeb9c4d88c001bb45ab", size = 1888894, upload-time = "2025-04-23T18:31:51.609Z" }, + { url = "https://files.pythonhosted.org/packages/46/8c/99040727b41f56616573a28771b1bfa08a3d3fe74d3d513f01251f79f172/pydantic_core-2.33.2-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:1082dd3e2d7109ad8b7da48e1d4710c8d06c253cbc4a27c1cff4fbcaa97a9e3f", size = 2015688, upload-time = "2025-04-23T18:31:53.175Z" }, + { url = "https://files.pythonhosted.org/packages/3a/cc/5999d1eb705a6cefc31f0b4a90e9f7fc400539b1a1030529700cc1b51838/pydantic_core-2.33.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f517ca031dfc037a9c07e748cefd8d96235088b83b4f4ba8939105d20fa1dcd6", size = 1844808, upload-time = "2025-04-23T18:31:54.79Z" }, + { url = "https://files.pythonhosted.org/packages/6f/5e/a0a7b8885c98889a18b6e376f344da1ef323d270b44edf8174d6bce4d622/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0a9f2c9dd19656823cb8250b0724ee9c60a82f3cdf68a080979d13092a3b0fef", size = 1885580, upload-time = "2025-04-23T18:31:57.393Z" }, + { url = "https://files.pythonhosted.org/packages/3b/2a/953581f343c7d11a304581156618c3f592435523dd9d79865903272c256a/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2b0a451c263b01acebe51895bfb0e1cc842a5c666efe06cdf13846c7418caa9a", size = 1973859, upload-time = "2025-04-23T18:31:59.065Z" }, + { url = "https://files.pythonhosted.org/packages/e6/55/f1a813904771c03a3f97f676c62cca0c0a4138654107c1b61f19c644868b/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ea40a64d23faa25e62a70ad163571c0b342b8bf66d5fa612ac0dec4f069d916", size = 2120810, upload-time = "2025-04-23T18:32:00.78Z" }, + { url = "https://files.pythonhosted.org/packages/aa/c3/053389835a996e18853ba107a63caae0b9deb4a276c6b472931ea9ae6e48/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0fb2d542b4d66f9470e8065c5469ec676978d625a8b7a363f07d9a501a9cb36a", size = 2676498, upload-time = "2025-04-23T18:32:02.418Z" }, + { url = "https://files.pythonhosted.org/packages/eb/3c/f4abd740877a35abade05e437245b192f9d0ffb48bbbbd708df33d3cda37/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9fdac5d6ffa1b5a83bca06ffe7583f5576555e6c8b3a91fbd25ea7780f825f7d", size = 2000611, upload-time = "2025-04-23T18:32:04.152Z" }, + { url = "https://files.pythonhosted.org/packages/59/a7/63ef2fed1837d1121a894d0ce88439fe3e3b3e48c7543b2a4479eb99c2bd/pydantic_core-2.33.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:04a1a413977ab517154eebb2d326da71638271477d6ad87a769102f7c2488c56", size = 2107924, upload-time = "2025-04-23T18:32:06.129Z" }, + { url = "https://files.pythonhosted.org/packages/04/8f/2551964ef045669801675f1cfc3b0d74147f4901c3ffa42be2ddb1f0efc4/pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:c8e7af2f4e0194c22b5b37205bfb293d166a7344a5b0d0eaccebc376546d77d5", size = 2063196, upload-time = "2025-04-23T18:32:08.178Z" }, + { url = "https://files.pythonhosted.org/packages/26/bd/d9602777e77fc6dbb0c7db9ad356e9a985825547dce5ad1d30ee04903918/pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:5c92edd15cd58b3c2d34873597a1e20f13094f59cf88068adb18947df5455b4e", size = 2236389, upload-time = "2025-04-23T18:32:10.242Z" }, + { url = "https://files.pythonhosted.org/packages/42/db/0e950daa7e2230423ab342ae918a794964b053bec24ba8af013fc7c94846/pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:65132b7b4a1c0beded5e057324b7e16e10910c106d43675d9bd87d4f38dde162", size = 2239223, upload-time = "2025-04-23T18:32:12.382Z" }, + { url = "https://files.pythonhosted.org/packages/58/4d/4f937099c545a8a17eb52cb67fe0447fd9a373b348ccfa9a87f141eeb00f/pydantic_core-2.33.2-cp313-cp313-win32.whl", hash = "sha256:52fb90784e0a242bb96ec53f42196a17278855b0f31ac7c3cc6f5c1ec4811849", size = 1900473, upload-time = "2025-04-23T18:32:14.034Z" }, + { url = "https://files.pythonhosted.org/packages/a0/75/4a0a9bac998d78d889def5e4ef2b065acba8cae8c93696906c3a91f310ca/pydantic_core-2.33.2-cp313-cp313-win_amd64.whl", hash = "sha256:c083a3bdd5a93dfe480f1125926afcdbf2917ae714bdb80b36d34318b2bec5d9", size = 1955269, upload-time = "2025-04-23T18:32:15.783Z" }, + { url = "https://files.pythonhosted.org/packages/f9/86/1beda0576969592f1497b4ce8e7bc8cbdf614c352426271b1b10d5f0aa64/pydantic_core-2.33.2-cp313-cp313-win_arm64.whl", hash = "sha256:e80b087132752f6b3d714f041ccf74403799d3b23a72722ea2e6ba2e892555b9", size = 1893921, upload-time = "2025-04-23T18:32:18.473Z" }, + { url = "https://files.pythonhosted.org/packages/a4/7d/e09391c2eebeab681df2b74bfe6c43422fffede8dc74187b2b0bf6fd7571/pydantic_core-2.33.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:61c18fba8e5e9db3ab908620af374db0ac1baa69f0f32df4f61ae23f15e586ac", size = 1806162, upload-time = "2025-04-23T18:32:20.188Z" }, + { url = "https://files.pythonhosted.org/packages/f1/3d/847b6b1fed9f8ed3bb95a9ad04fbd0b212e832d4f0f50ff4d9ee5a9f15cf/pydantic_core-2.33.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95237e53bb015f67b63c91af7518a62a8660376a6a0db19b89acc77a4d6199f5", size = 1981560, upload-time = "2025-04-23T18:32:22.354Z" }, + { url = "https://files.pythonhosted.org/packages/6f/9a/e73262f6c6656262b5fdd723ad90f518f579b7bc8622e43a942eec53c938/pydantic_core-2.33.2-cp313-cp313t-win_amd64.whl", hash = "sha256:c2fc0a768ef76c15ab9238afa6da7f69895bb5d1ee83aeea2e3509af4472d0b9", size = 1935777, upload-time = "2025-04-23T18:32:25.088Z" }, +] + +[[package]] +name = "pygments" +version = "2.19.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631, upload-time = "2025-06-21T13:39:12.283Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" }, +] + +[[package]] +name = "pyparsing" +version = "3.2.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/bb/22/f1129e69d94ffff626bdb5c835506b3a5b4f3d070f17ea295e12c2c6f60f/pyparsing-3.2.3.tar.gz", hash = "sha256:b9c13f1ab8b3b542f72e28f634bad4de758ab3ce4546e4301970ad6fa77c38be", size = 1088608, upload-time = "2025-03-25T05:01:28.114Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/05/e7/df2285f3d08fee213f2d041540fa4fc9ca6c2d44cf36d3a035bf2a8d2bcc/pyparsing-3.2.3-py3-none-any.whl", hash = "sha256:a749938e02d6fd0b59b356ca504a24982314bb090c383e3cf201c95ef7e2bfcf", size = 111120, upload-time = "2025-03-25T05:01:24.908Z" }, +] + +[[package]] +name = "pyserial" +version = "3.5" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1e/7d/ae3f0a63f41e4d2f6cb66a5b57197850f919f59e558159a4dd3a818f5082/pyserial-3.5.tar.gz", hash = "sha256:3c77e014170dfffbd816e6ffc205e9842efb10be9f58ec16d3e8675b4925cddb", size = 159125, upload-time = "2020-11-23T03:59:15.045Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/07/bc/587a445451b253b285629263eb51c2d8e9bcea4fc97826266d186f96f558/pyserial-3.5-py2.py3-none-any.whl", hash = "sha256:c4451db6ba391ca6ca299fb3ec7bae67a5c55dde170964c7a14ceefec02f2cf0", size = 90585, upload-time = "2020-11-23T03:59:13.41Z" }, +] + +[[package]] +name = "python-dateutil" +version = "2.9.0.post0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "six" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/66/c0/0c8b6ad9f17a802ee498c46e004a0eb49bc148f2fd230864601a86dcf6db/python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 342432, upload-time = "2024-03-01T18:36:20.211Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892, upload-time = "2024-03-01T18:36:18.57Z" }, +] + +[[package]] +name = "pytz" +version = "2025.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f8/bf/abbd3cdfb8fbc7fb3d4d38d320f2441b1e7cbe29be4f23797b4a2b5d8aac/pytz-2025.2.tar.gz", hash = "sha256:360b9e3dbb49a209c21ad61809c7fb453643e048b38924c765813546746e81c3", size = 320884, upload-time = "2025-03-25T02:25:00.538Z" } +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 = "pyyaml" +version = "6.0.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/54/ed/79a089b6be93607fa5cdaedf301d7dfb23af5f25c398d5ead2525b063e17/pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e", size = 130631, upload-time = "2024-08-06T20:33:50.674Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/86/0c/c581167fc46d6d6d7ddcfb8c843a4de25bdd27e4466938109ca68492292c/PyYAML-6.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab", size = 183873, upload-time = "2024-08-06T20:32:25.131Z" }, + { url = "https://files.pythonhosted.org/packages/a8/0c/38374f5bb272c051e2a69281d71cba6fdb983413e6758b84482905e29a5d/PyYAML-6.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725", size = 173302, upload-time = "2024-08-06T20:32:26.511Z" }, + { url = "https://files.pythonhosted.org/packages/c3/93/9916574aa8c00aa06bbac729972eb1071d002b8e158bd0e83a3b9a20a1f7/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5", size = 739154, upload-time = "2024-08-06T20:32:28.363Z" }, + { url = "https://files.pythonhosted.org/packages/95/0f/b8938f1cbd09739c6da569d172531567dbcc9789e0029aa070856f123984/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425", size = 766223, upload-time = "2024-08-06T20:32:30.058Z" }, + { url = "https://files.pythonhosted.org/packages/b9/2b/614b4752f2e127db5cc206abc23a8c19678e92b23c3db30fc86ab731d3bd/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476", size = 767542, upload-time = "2024-08-06T20:32:31.881Z" }, + { url = "https://files.pythonhosted.org/packages/d4/00/dd137d5bcc7efea1836d6264f049359861cf548469d18da90cd8216cf05f/PyYAML-6.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48", size = 731164, upload-time = "2024-08-06T20:32:37.083Z" }, + { url = "https://files.pythonhosted.org/packages/c9/1f/4f998c900485e5c0ef43838363ba4a9723ac0ad73a9dc42068b12aaba4e4/PyYAML-6.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b", size = 756611, upload-time = "2024-08-06T20:32:38.898Z" }, + { url = "https://files.pythonhosted.org/packages/df/d1/f5a275fdb252768b7a11ec63585bc38d0e87c9e05668a139fea92b80634c/PyYAML-6.0.2-cp312-cp312-win32.whl", hash = "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4", size = 140591, upload-time = "2024-08-06T20:32:40.241Z" }, + { url = "https://files.pythonhosted.org/packages/0c/e8/4f648c598b17c3d06e8753d7d13d57542b30d56e6c2dedf9c331ae56312e/PyYAML-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8", size = 156338, upload-time = "2024-08-06T20:32:41.93Z" }, + { url = "https://files.pythonhosted.org/packages/ef/e3/3af305b830494fa85d95f6d95ef7fa73f2ee1cc8ef5b495c7c3269fb835f/PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba", size = 181309, upload-time = "2024-08-06T20:32:43.4Z" }, + { url = "https://files.pythonhosted.org/packages/45/9f/3b1c20a0b7a3200524eb0076cc027a970d320bd3a6592873c85c92a08731/PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1", size = 171679, upload-time = "2024-08-06T20:32:44.801Z" }, + { url = "https://files.pythonhosted.org/packages/7c/9a/337322f27005c33bcb656c655fa78325b730324c78620e8328ae28b64d0c/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133", size = 733428, upload-time = "2024-08-06T20:32:46.432Z" }, + { url = "https://files.pythonhosted.org/packages/a3/69/864fbe19e6c18ea3cc196cbe5d392175b4cf3d5d0ac1403ec3f2d237ebb5/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484", size = 763361, upload-time = "2024-08-06T20:32:51.188Z" }, + { url = "https://files.pythonhosted.org/packages/04/24/b7721e4845c2f162d26f50521b825fb061bc0a5afcf9a386840f23ea19fa/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5", size = 759523, upload-time = "2024-08-06T20:32:53.019Z" }, + { url = "https://files.pythonhosted.org/packages/2b/b2/e3234f59ba06559c6ff63c4e10baea10e5e7df868092bf9ab40e5b9c56b6/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc", size = 726660, upload-time = "2024-08-06T20:32:54.708Z" }, + { url = "https://files.pythonhosted.org/packages/fe/0f/25911a9f080464c59fab9027482f822b86bf0608957a5fcc6eaac85aa515/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652", size = 751597, upload-time = "2024-08-06T20:32:56.985Z" }, + { url = "https://files.pythonhosted.org/packages/14/0d/e2c3b43bbce3cf6bd97c840b46088a3031085179e596d4929729d8d68270/PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183", size = 140527, upload-time = "2024-08-06T20:33:03.001Z" }, + { url = "https://files.pythonhosted.org/packages/fa/de/02b54f42487e3d3c6efb3f89428677074ca7bf43aae402517bc7cca949f3/PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563", size = 156446, upload-time = "2024-08-06T20:33:04.33Z" }, +] + +[[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 = "rich" +version = "14.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markdown-it-py" }, + { name = "pygments" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/fe/75/af448d8e52bf1d8fa6a9d089ca6c07ff4453d86c65c145d0a300bb073b9b/rich-14.1.0.tar.gz", hash = "sha256:e497a48b844b0320d45007cdebfeaeed8db2a4f4bcf49f15e455cfc4af11eaa8", size = 224441, upload-time = "2025-07-25T07:32:58.125Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e3/30/3c4d035596d3cf444529e0b2953ad0466f6049528a879d27534700580395/rich-14.1.0-py3-none-any.whl", hash = "sha256:536f5f1785986d6dbdea3c75205c473f970777b4a0d6c6dd1b696aa05a3fa04f", size = 243368, upload-time = "2025-07-25T07:32:56.73Z" }, +] + +[[package]] +name = "scipy" +version = "1.16.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f5/4a/b927028464795439faec8eaf0b03b011005c487bb2d07409f28bf30879c4/scipy-1.16.1.tar.gz", hash = "sha256:44c76f9e8b6e8e488a586190ab38016e4ed2f8a038af7cd3defa903c0a2238b3", size = 30580861, upload-time = "2025-07-27T16:33:30.834Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f8/d9/ec4864f5896232133f51382b54a08de91a9d1af7a76dfa372894026dfee2/scipy-1.16.1-cp312-cp312-macosx_10_14_x86_64.whl", hash = "sha256:81b433bbeaf35728dad619afc002db9b189e45eebe2cd676effe1fb93fef2b9c", size = 36575194, upload-time = "2025-07-27T16:27:41.321Z" }, + { url = "https://files.pythonhosted.org/packages/5c/6d/40e81ecfb688e9d25d34a847dca361982a6addf8e31f0957b1a54fbfa994/scipy-1.16.1-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:886cc81fdb4c6903a3bb0464047c25a6d1016fef77bb97949817d0c0d79f9e04", size = 28594590, upload-time = "2025-07-27T16:27:49.204Z" }, + { url = "https://files.pythonhosted.org/packages/0e/37/9f65178edfcc629377ce9a64fc09baebea18c80a9e57ae09a52edf84880b/scipy-1.16.1-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:15240c3aac087a522b4eaedb09f0ad061753c5eebf1ea430859e5bf8640d5919", size = 20866458, upload-time = "2025-07-27T16:27:54.98Z" }, + { url = "https://files.pythonhosted.org/packages/2c/7b/749a66766871ea4cb1d1ea10f27004db63023074c22abed51f22f09770e0/scipy-1.16.1-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:65f81a25805f3659b48126b5053d9e823d3215e4a63730b5e1671852a1705921", size = 23539318, upload-time = "2025-07-27T16:28:01.604Z" }, + { url = "https://files.pythonhosted.org/packages/c4/db/8d4afec60eb833a666434d4541a3151eedbf2494ea6d4d468cbe877f00cd/scipy-1.16.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:6c62eea7f607f122069b9bad3f99489ddca1a5173bef8a0c75555d7488b6f725", size = 33292899, upload-time = "2025-07-27T16:28:09.147Z" }, + { url = "https://files.pythonhosted.org/packages/51/1e/79023ca3bbb13a015d7d2757ecca3b81293c663694c35d6541b4dca53e98/scipy-1.16.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f965bbf3235b01c776115ab18f092a95aa74c271a52577bcb0563e85738fd618", size = 35162637, upload-time = "2025-07-27T16:28:17.535Z" }, + { url = "https://files.pythonhosted.org/packages/b6/49/0648665f9c29fdaca4c679182eb972935b3b4f5ace41d323c32352f29816/scipy-1.16.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:f006e323874ffd0b0b816d8c6a8e7f9a73d55ab3b8c3f72b752b226d0e3ac83d", size = 35490507, upload-time = "2025-07-27T16:28:25.705Z" }, + { url = "https://files.pythonhosted.org/packages/62/8f/66cbb9d6bbb18d8c658f774904f42a92078707a7c71e5347e8bf2f52bb89/scipy-1.16.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e8fd15fc5085ab4cca74cb91fe0a4263b1f32e4420761ddae531ad60934c2119", size = 37923998, upload-time = "2025-07-27T16:28:34.339Z" }, + { url = "https://files.pythonhosted.org/packages/14/c3/61f273ae550fbf1667675701112e380881905e28448c080b23b5a181df7c/scipy-1.16.1-cp312-cp312-win_amd64.whl", hash = "sha256:f7b8013c6c066609577d910d1a2a077021727af07b6fab0ee22c2f901f22352a", size = 38508060, upload-time = "2025-07-27T16:28:43.242Z" }, + { url = "https://files.pythonhosted.org/packages/93/0b/b5c99382b839854a71ca9482c684e3472badc62620287cbbdab499b75ce6/scipy-1.16.1-cp313-cp313-macosx_10_14_x86_64.whl", hash = "sha256:5451606823a5e73dfa621a89948096c6528e2896e40b39248295d3a0138d594f", size = 36533717, upload-time = "2025-07-27T16:28:51.706Z" }, + { url = "https://files.pythonhosted.org/packages/eb/e5/69ab2771062c91e23e07c12e7d5033a6b9b80b0903ee709c3c36b3eb520c/scipy-1.16.1-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:89728678c5ca5abd610aee148c199ac1afb16e19844401ca97d43dc548a354eb", size = 28570009, upload-time = "2025-07-27T16:28:57.017Z" }, + { url = "https://files.pythonhosted.org/packages/f4/69/bd75dbfdd3cf524f4d753484d723594aed62cfaac510123e91a6686d520b/scipy-1.16.1-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:e756d688cb03fd07de0fffad475649b03cb89bee696c98ce508b17c11a03f95c", size = 20841942, upload-time = "2025-07-27T16:29:01.152Z" }, + { url = "https://files.pythonhosted.org/packages/ea/74/add181c87663f178ba7d6144b370243a87af8476664d5435e57d599e6874/scipy-1.16.1-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:5aa2687b9935da3ed89c5dbed5234576589dd28d0bf7cd237501ccfbdf1ad608", size = 23498507, upload-time = "2025-07-27T16:29:05.202Z" }, + { url = "https://files.pythonhosted.org/packages/1d/74/ece2e582a0d9550cee33e2e416cc96737dce423a994d12bbe59716f47ff1/scipy-1.16.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:0851f6a1e537fe9399f35986897e395a1aa61c574b178c0d456be5b1a0f5ca1f", size = 33286040, upload-time = "2025-07-27T16:29:10.201Z" }, + { url = "https://files.pythonhosted.org/packages/e4/82/08e4076df538fb56caa1d489588d880ec7c52d8273a606bb54d660528f7c/scipy-1.16.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:fedc2cbd1baed37474b1924c331b97bdff611d762c196fac1a9b71e67b813b1b", size = 35176096, upload-time = "2025-07-27T16:29:17.091Z" }, + { url = "https://files.pythonhosted.org/packages/fa/79/cd710aab8c921375711a8321c6be696e705a120e3011a643efbbcdeeabcc/scipy-1.16.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:2ef500e72f9623a6735769e4b93e9dcb158d40752cdbb077f305487e3e2d1f45", size = 35490328, upload-time = "2025-07-27T16:29:22.928Z" }, + { url = "https://files.pythonhosted.org/packages/71/73/e9cc3d35ee4526d784520d4494a3e1ca969b071fb5ae5910c036a375ceec/scipy-1.16.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:978d8311674b05a8f7ff2ea6c6bce5d8b45a0cb09d4c5793e0318f448613ea65", size = 37939921, upload-time = "2025-07-27T16:29:29.108Z" }, + { url = "https://files.pythonhosted.org/packages/21/12/c0efd2941f01940119b5305c375ae5c0fcb7ec193f806bd8f158b73a1782/scipy-1.16.1-cp313-cp313-win_amd64.whl", hash = "sha256:81929ed0fa7a5713fcdd8b2e6f73697d3b4c4816d090dd34ff937c20fa90e8ab", size = 38479462, upload-time = "2025-07-27T16:30:24.078Z" }, + { url = "https://files.pythonhosted.org/packages/7a/19/c3d08b675260046a991040e1ea5d65f91f40c7df1045fffff412dcfc6765/scipy-1.16.1-cp313-cp313t-macosx_10_14_x86_64.whl", hash = "sha256:bcc12db731858abda693cecdb3bdc9e6d4bd200213f49d224fe22df82687bdd6", size = 36938832, upload-time = "2025-07-27T16:29:35.057Z" }, + { url = "https://files.pythonhosted.org/packages/81/f2/ce53db652c033a414a5b34598dba6b95f3d38153a2417c5a3883da429029/scipy-1.16.1-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:744d977daa4becb9fc59135e75c069f8d301a87d64f88f1e602a9ecf51e77b27", size = 29093084, upload-time = "2025-07-27T16:29:40.201Z" }, + { url = "https://files.pythonhosted.org/packages/a9/ae/7a10ff04a7dc15f9057d05b33737ade244e4bd195caa3f7cc04d77b9e214/scipy-1.16.1-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:dc54f76ac18073bcecffb98d93f03ed6b81a92ef91b5d3b135dcc81d55a724c7", size = 21365098, upload-time = "2025-07-27T16:29:44.295Z" }, + { url = "https://files.pythonhosted.org/packages/36/ac/029ff710959932ad3c2a98721b20b405f05f752f07344622fd61a47c5197/scipy-1.16.1-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:367d567ee9fc1e9e2047d31f39d9d6a7a04e0710c86e701e053f237d14a9b4f6", size = 23896858, upload-time = "2025-07-27T16:29:48.784Z" }, + { url = "https://files.pythonhosted.org/packages/71/13/d1ef77b6bd7898720e1f0b6b3743cb945f6c3cafa7718eaac8841035ab60/scipy-1.16.1-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:4cf5785e44e19dcd32a0e4807555e1e9a9b8d475c6afff3d21c3c543a6aa84f4", size = 33438311, upload-time = "2025-07-27T16:29:54.164Z" }, + { url = "https://files.pythonhosted.org/packages/2d/e0/e64a6821ffbb00b4c5b05169f1c1fddb4800e9307efe3db3788995a82a2c/scipy-1.16.1-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3d0b80fb26d3e13a794c71d4b837e2a589d839fd574a6bbb4ee1288c213ad4a3", size = 35279542, upload-time = "2025-07-27T16:30:00.249Z" }, + { url = "https://files.pythonhosted.org/packages/57/59/0dc3c8b43e118f1e4ee2b798dcc96ac21bb20014e5f1f7a8e85cc0653bdb/scipy-1.16.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:8503517c44c18d1030d666cb70aaac1cc8913608816e06742498833b128488b7", size = 35667665, upload-time = "2025-07-27T16:30:05.916Z" }, + { url = "https://files.pythonhosted.org/packages/45/5f/844ee26e34e2f3f9f8febb9343748e72daeaec64fe0c70e9bf1ff84ec955/scipy-1.16.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:30cc4bb81c41831ecfd6dc450baf48ffd80ef5aed0f5cf3ea775740e80f16ecc", size = 38045210, upload-time = "2025-07-27T16:30:11.655Z" }, + { url = "https://files.pythonhosted.org/packages/8d/d7/210f2b45290f444f1de64bc7353aa598ece9f0e90c384b4a156f9b1a5063/scipy-1.16.1-cp313-cp313t-win_amd64.whl", hash = "sha256:c24fa02f7ed23ae514460a22c57eca8f530dbfa50b1cfdbf4f37c05b5309cc39", size = 38593661, upload-time = "2025-07-27T16:30:17.825Z" }, + { url = "https://files.pythonhosted.org/packages/81/ea/84d481a5237ed223bd3d32d6e82d7a6a96e34756492666c260cef16011d1/scipy-1.16.1-cp314-cp314-macosx_10_14_x86_64.whl", hash = "sha256:796a5a9ad36fa3a782375db8f4241ab02a091308eb079746bc0f874c9b998318", size = 36525921, upload-time = "2025-07-27T16:30:30.081Z" }, + { url = "https://files.pythonhosted.org/packages/4e/9f/d9edbdeff9f3a664807ae3aea383e10afaa247e8e6255e6d2aa4515e8863/scipy-1.16.1-cp314-cp314-macosx_12_0_arm64.whl", hash = "sha256:3ea0733a2ff73fd6fdc5fecca54ee9b459f4d74f00b99aced7d9a3adb43fb1cc", size = 28564152, upload-time = "2025-07-27T16:30:35.336Z" }, + { url = "https://files.pythonhosted.org/packages/3b/95/8125bcb1fe04bc267d103e76516243e8d5e11229e6b306bda1024a5423d1/scipy-1.16.1-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:85764fb15a2ad994e708258bb4ed8290d1305c62a4e1ef07c414356a24fcfbf8", size = 20836028, upload-time = "2025-07-27T16:30:39.421Z" }, + { url = "https://files.pythonhosted.org/packages/77/9c/bf92e215701fc70bbcd3d14d86337cf56a9b912a804b9c776a269524a9e9/scipy-1.16.1-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:ca66d980469cb623b1759bdd6e9fd97d4e33a9fad5b33771ced24d0cb24df67e", size = 23489666, upload-time = "2025-07-27T16:30:43.663Z" }, + { url = "https://files.pythonhosted.org/packages/5e/00/5e941d397d9adac41b02839011594620d54d99488d1be5be755c00cde9ee/scipy-1.16.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:e7cc1ffcc230f568549fc56670bcf3df1884c30bd652c5da8138199c8c76dae0", size = 33358318, upload-time = "2025-07-27T16:30:48.982Z" }, + { url = "https://files.pythonhosted.org/packages/0e/87/8db3aa10dde6e3e8e7eb0133f24baa011377d543f5b19c71469cf2648026/scipy-1.16.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3ddfb1e8d0b540cb4ee9c53fc3dea3186f97711248fb94b4142a1b27178d8b4b", size = 35185724, upload-time = "2025-07-27T16:30:54.26Z" }, + { url = "https://files.pythonhosted.org/packages/89/b4/6ab9ae443216807622bcff02690262d8184078ea467efee2f8c93288a3b1/scipy-1.16.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:4dc0e7be79e95d8ba3435d193e0d8ce372f47f774cffd882f88ea4e1e1ddc731", size = 35554335, upload-time = "2025-07-27T16:30:59.765Z" }, + { url = "https://files.pythonhosted.org/packages/9c/9a/d0e9dc03c5269a1afb60661118296a32ed5d2c24298af61b676c11e05e56/scipy-1.16.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:f23634f9e5adb51b2a77766dac217063e764337fbc816aa8ad9aaebcd4397fd3", size = 37960310, upload-time = "2025-07-27T16:31:06.151Z" }, + { url = "https://files.pythonhosted.org/packages/5e/00/c8f3130a50521a7977874817ca89e0599b1b4ee8e938bad8ae798a0e1f0d/scipy-1.16.1-cp314-cp314-win_amd64.whl", hash = "sha256:57d75524cb1c5a374958a2eae3d84e1929bb971204cc9d52213fb8589183fc19", size = 39319239, upload-time = "2025-07-27T16:31:59.942Z" }, + { url = "https://files.pythonhosted.org/packages/f2/f2/1ca3eda54c3a7e4c92f6acef7db7b3a057deb135540d23aa6343ef8ad333/scipy-1.16.1-cp314-cp314t-macosx_10_14_x86_64.whl", hash = "sha256:d8da7c3dd67bcd93f15618938f43ed0995982eb38973023d46d4646c4283ad65", size = 36939460, upload-time = "2025-07-27T16:31:11.865Z" }, + { url = "https://files.pythonhosted.org/packages/80/30/98c2840b293a132400c0940bb9e140171dcb8189588619048f42b2ce7b4f/scipy-1.16.1-cp314-cp314t-macosx_12_0_arm64.whl", hash = "sha256:cc1d2f2fd48ba1e0620554fe5bc44d3e8f5d4185c8c109c7fbdf5af2792cfad2", size = 29093322, upload-time = "2025-07-27T16:31:17.045Z" }, + { url = "https://files.pythonhosted.org/packages/c1/e6/1e6e006e850622cf2a039b62d1a6ddc4497d4851e58b68008526f04a9a00/scipy-1.16.1-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:21a611ced9275cb861bacadbada0b8c0623bc00b05b09eb97f23b370fc2ae56d", size = 21365329, upload-time = "2025-07-27T16:31:21.188Z" }, + { url = "https://files.pythonhosted.org/packages/8e/02/72a5aa5b820589dda9a25e329ca752842bfbbaf635e36bc7065a9b42216e/scipy-1.16.1-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:8dfbb25dffc4c3dd9371d8ab456ca81beeaf6f9e1c2119f179392f0dc1ab7695", size = 23897544, upload-time = "2025-07-27T16:31:25.408Z" }, + { url = "https://files.pythonhosted.org/packages/2b/dc/7122d806a6f9eb8a33532982234bed91f90272e990f414f2830cfe656e0b/scipy-1.16.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f0ebb7204f063fad87fc0a0e4ff4a2ff40b2a226e4ba1b7e34bf4b79bf97cd86", size = 33442112, upload-time = "2025-07-27T16:31:30.62Z" }, + { url = "https://files.pythonhosted.org/packages/24/39/e383af23564daa1021a5b3afbe0d8d6a68ec639b943661841f44ac92de85/scipy-1.16.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f1b9e5962656f2734c2b285a8745358ecb4e4efbadd00208c80a389227ec61ff", size = 35286594, upload-time = "2025-07-27T16:31:36.112Z" }, + { url = "https://files.pythonhosted.org/packages/95/47/1a0b0aff40c3056d955f38b0df5d178350c3d74734ec54f9c68d23910be5/scipy-1.16.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:5e1a106f8c023d57a2a903e771228bf5c5b27b5d692088f457acacd3b54511e4", size = 35665080, upload-time = "2025-07-27T16:31:42.025Z" }, + { url = "https://files.pythonhosted.org/packages/64/df/ce88803e9ed6e27fe9b9abefa157cf2c80e4fa527cf17ee14be41f790ad4/scipy-1.16.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:709559a1db68a9abc3b2c8672c4badf1614f3b440b3ab326d86a5c0491eafae3", size = 38050306, upload-time = "2025-07-27T16:31:48.109Z" }, + { url = "https://files.pythonhosted.org/packages/6e/6c/a76329897a7cae4937d403e623aa6aaea616a0bb5b36588f0b9d1c9a3739/scipy-1.16.1-cp314-cp314t-win_amd64.whl", hash = "sha256:c0c804d60492a0aad7f5b2bb1862f4548b990049e27e828391ff2bf6f7199998", size = 39427705, upload-time = "2025-07-27T16:31:53.96Z" }, +] + +[[package]] +name = "setuptools" +version = "80.9.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/18/5d/3bf57dcd21979b887f014ea83c24ae194cfcd12b9e0fda66b957c69d1fca/setuptools-80.9.0.tar.gz", hash = "sha256:f36b47402ecde768dbfafc46e8e4207b4360c654f1f3bb84475f0a28628fb19c", size = 1319958, upload-time = "2025-05-27T00:56:51.443Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a3/dc/17031897dae0efacfea57dfd3a82fdd2a2aeb58e0ff71b77b87e44edc772/setuptools-80.9.0-py3-none-any.whl", hash = "sha256:062d34222ad13e0cc312a4c02d73f059e86a4acbfbdea8f8f76b28c99f306922", size = 1201486, upload-time = "2025-05-27T00:56:49.664Z" }, +] + +[[package]] +name = "shellingham" +version = "1.5.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/58/15/8b3609fd3830ef7b27b655beb4b4e9c62313a4e8da8c676e142cc210d58e/shellingham-1.5.4.tar.gz", hash = "sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de", size = 10310, upload-time = "2023-10-24T04:13:40.426Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e0/f9/0595336914c5619e5f28a1fb793285925a8cd4b432c9da0a987836c7f822/shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686", size = 9755, upload-time = "2023-10-24T04:13:38.866Z" }, +] + +[[package]] +name = "six" +version = "1.17.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81", size = 34031, upload-time = "2024-12-04T17:35:28.174Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050, upload-time = "2024-12-04T17:35:26.475Z" }, +] + +[[package]] +name = "sniffio" +version = "1.3.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372, upload-time = "2024-02-25T23:20:04.057Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235, upload-time = "2024-02-25T23:20:01.196Z" }, +] + +[[package]] +name = "starlette" +version = "0.47.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/04/57/d062573f391d062710d4088fa1369428c38d51460ab6fedff920efef932e/starlette-0.47.2.tar.gz", hash = "sha256:6ae9aa5db235e4846decc1e7b79c4f346adf41e9777aebeb49dfd09bbd7023d8", size = 2583948, upload-time = "2025-07-20T17:31:58.522Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f7/1f/b876b1f83aef204198a42dc101613fefccb32258e5428b5f9259677864b4/starlette-0.47.2-py3-none-any.whl", hash = "sha256:c5847e96134e5c5371ee9fac6fdf1a67336d5815e09eb2a01fdb57a351ef915b", size = 72984, upload-time = "2025-07-20T17:31:56.738Z" }, +] + +[[package]] +name = "sympy" +version = "1.14.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mpmath" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/83/d3/803453b36afefb7c2bb238361cd4ae6125a569b4db67cd9e79846ba2d68c/sympy-1.14.0.tar.gz", hash = "sha256:d3d3fe8df1e5a0b42f0e7bdf50541697dbe7d23746e894990c030e2b05e72517", size = 7793921, upload-time = "2025-04-27T18:05:01.611Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a2/09/77d55d46fd61b4a135c444fc97158ef34a095e5681d0a6c10b75bf356191/sympy-1.14.0-py3-none-any.whl", hash = "sha256:e091cc3e99d2141a0ba2847328f5479b05d94a6635cb96148ccb3f34671bd8f5", size = 6299353, upload-time = "2025-04-27T18:04:59.103Z" }, +] + +[[package]] +name = "termcolor" +version = "3.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ca/6c/3d75c196ac07ac8749600b60b03f4f6094d54e132c4d94ebac6ee0e0add0/termcolor-3.1.0.tar.gz", hash = "sha256:6a6dd7fbee581909eeec6a756cff1d7f7c376063b14e4a298dc4980309e55970", size = 14324, upload-time = "2025-04-30T11:37:53.791Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4f/bd/de8d508070629b6d84a30d01d57e4a65c69aa7f5abe7560b8fad3b50ea59/termcolor-3.1.0-py3-none-any.whl", hash = "sha256:591dd26b5c2ce03b9e43f391264626557873ce1d379019786f99b0c2bee140aa", size = 7684, upload-time = "2025-04-30T11:37:52.382Z" }, +] + +[[package]] +name = "torch" +version = "2.8.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "filelock" }, + { name = "fsspec" }, + { name = "jinja2" }, + { name = "networkx" }, + { name = "nvidia-cublas-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, + { name = "nvidia-cuda-cupti-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, + { name = "nvidia-cuda-nvrtc-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, + { name = "nvidia-cuda-runtime-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, + { name = "nvidia-cudnn-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, + { name = "nvidia-cufft-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, + { name = "nvidia-cufile-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, + { name = "nvidia-curand-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, + { name = "nvidia-cusolver-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, + { name = "nvidia-cusparse-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, + { name = "nvidia-cusparselt-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, + { name = "nvidia-nccl-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, + { name = "nvidia-nvjitlink-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, + { name = "nvidia-nvtx-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, + { name = "setuptools" }, + { name = "sympy" }, + { name = "triton", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, + { name = "typing-extensions" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/49/0c/2fd4df0d83a495bb5e54dca4474c4ec5f9c62db185421563deeb5dabf609/torch-2.8.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:e2fab4153768d433f8ed9279c8133a114a034a61e77a3a104dcdf54388838705", size = 101906089, upload-time = "2025-08-06T14:53:52.631Z" }, + { url = "https://files.pythonhosted.org/packages/99/a8/6acf48d48838fb8fe480597d98a0668c2beb02ee4755cc136de92a0a956f/torch-2.8.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:b2aca0939fb7e4d842561febbd4ffda67a8e958ff725c1c27e244e85e982173c", size = 887913624, upload-time = "2025-08-06T14:56:44.33Z" }, + { url = "https://files.pythonhosted.org/packages/af/8a/5c87f08e3abd825c7dfecef5a0f1d9aa5df5dd0e3fd1fa2f490a8e512402/torch-2.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:2f4ac52f0130275d7517b03a33d2493bab3693c83dcfadf4f81688ea82147d2e", size = 241326087, upload-time = "2025-08-06T14:53:46.503Z" }, + { url = "https://files.pythonhosted.org/packages/be/66/5c9a321b325aaecb92d4d1855421e3a055abd77903b7dab6575ca07796db/torch-2.8.0-cp312-none-macosx_11_0_arm64.whl", hash = "sha256:619c2869db3ada2c0105487ba21b5008defcc472d23f8b80ed91ac4a380283b0", size = 73630478, upload-time = "2025-08-06T14:53:57.144Z" }, + { url = "https://files.pythonhosted.org/packages/10/4e/469ced5a0603245d6a19a556e9053300033f9c5baccf43a3d25ba73e189e/torch-2.8.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:2b2f96814e0345f5a5aed9bf9734efa913678ed19caf6dc2cddb7930672d6128", size = 101936856, upload-time = "2025-08-06T14:54:01.526Z" }, + { url = "https://files.pythonhosted.org/packages/16/82/3948e54c01b2109238357c6f86242e6ecbf0c63a1af46906772902f82057/torch-2.8.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:65616ca8ec6f43245e1f5f296603e33923f4c30f93d65e103d9e50c25b35150b", size = 887922844, upload-time = "2025-08-06T14:55:50.78Z" }, + { url = "https://files.pythonhosted.org/packages/e3/54/941ea0a860f2717d86a811adf0c2cd01b3983bdd460d0803053c4e0b8649/torch-2.8.0-cp313-cp313-win_amd64.whl", hash = "sha256:659df54119ae03e83a800addc125856effda88b016dfc54d9f65215c3975be16", size = 241330968, upload-time = "2025-08-06T14:54:45.293Z" }, + { url = "https://files.pythonhosted.org/packages/de/69/8b7b13bba430f5e21d77708b616f767683629fc4f8037564a177d20f90ed/torch-2.8.0-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:1a62a1ec4b0498930e2543535cf70b1bef8c777713de7ceb84cd79115f553767", size = 73915128, upload-time = "2025-08-06T14:54:34.769Z" }, + { url = "https://files.pythonhosted.org/packages/15/0e/8a800e093b7f7430dbaefa80075aee9158ec22e4c4fc3c1a66e4fb96cb4f/torch-2.8.0-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:83c13411a26fac3d101fe8035a6b0476ae606deb8688e904e796a3534c197def", size = 102020139, upload-time = "2025-08-06T14:54:39.047Z" }, + { url = "https://files.pythonhosted.org/packages/4a/15/5e488ca0bc6162c86a33b58642bc577c84ded17c7b72d97e49b5833e2d73/torch-2.8.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:8f0a9d617a66509ded240add3754e462430a6c1fc5589f86c17b433dd808f97a", size = 887990692, upload-time = "2025-08-06T14:56:18.286Z" }, + { url = "https://files.pythonhosted.org/packages/b4/a8/6a04e4b54472fc5dba7ca2341ab219e529f3c07b6941059fbf18dccac31f/torch-2.8.0-cp313-cp313t-win_amd64.whl", hash = "sha256:a7242b86f42be98ac674b88a4988643b9bc6145437ec8f048fea23f72feb5eca", size = 241603453, upload-time = "2025-08-06T14:55:22.945Z" }, + { 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 = "torchvision" +version = "0.23.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, + { name = "pillow" }, + { name = "torch" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/df/1d/0ea0b34bde92a86d42620f29baa6dcbb5c2fc85990316df5cb8f7abb8ea2/torchvision-0.23.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:e0e2c04a91403e8dd3af9756c6a024a1d9c0ed9c0d592a8314ded8f4fe30d440", size = 1856885, upload-time = "2025-08-06T14:58:06.503Z" }, + { url = "https://files.pythonhosted.org/packages/e2/00/2f6454decc0cd67158c7890364e446aad4b91797087a57a78e72e1a8f8bc/torchvision-0.23.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:6dd7c4d329a0e03157803031bc856220c6155ef08c26d4f5bbac938acecf0948", size = 2396614, upload-time = "2025-08-06T14:58:03.116Z" }, + { url = "https://files.pythonhosted.org/packages/e4/b5/3e580dcbc16f39a324f3dd71b90edbf02a42548ad44d2b4893cc92b1194b/torchvision-0.23.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:4e7d31c43bc7cbecbb1a5652ac0106b436aa66e26437585fc2c4b2cf04d6014c", size = 8627108, upload-time = "2025-08-06T14:58:12.956Z" }, + { url = "https://files.pythonhosted.org/packages/82/c1/c2fe6d61e110a8d0de2f94276899a2324a8f1e6aee559eb6b4629ab27466/torchvision-0.23.0-cp312-cp312-win_amd64.whl", hash = "sha256:a2e45272abe7b8bf0d06c405e78521b5757be1bd0ed7e5cd78120f7fdd4cbf35", size = 1600723, upload-time = "2025-08-06T14:57:57.986Z" }, + { url = "https://files.pythonhosted.org/packages/91/37/45a5b9407a7900f71d61b2b2f62db4b7c632debca397f205fdcacb502780/torchvision-0.23.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1c37e325e09a184b730c3ef51424f383ec5745378dc0eca244520aca29722600", size = 1856886, upload-time = "2025-08-06T14:58:05.491Z" }, + { url = "https://files.pythonhosted.org/packages/ac/da/a06c60fc84fc849377cf035d3b3e9a1c896d52dbad493b963c0f1cdd74d0/torchvision-0.23.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:2f7fd6c15f3697e80627b77934f77705f3bc0e98278b989b2655de01f6903e1d", size = 2353112, upload-time = "2025-08-06T14:58:26.265Z" }, + { url = "https://files.pythonhosted.org/packages/a0/27/5ce65ba5c9d3b7d2ccdd79892ab86a2f87ac2ca6638f04bb0280321f1a9c/torchvision-0.23.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:a76fafe113b2977be3a21bf78f115438c1f88631d7a87203acb3dd6ae55889e6", size = 8627658, upload-time = "2025-08-06T14:58:15.999Z" }, + { url = "https://files.pythonhosted.org/packages/1f/e4/028a27b60aa578a2fa99d9d7334ff1871bb17008693ea055a2fdee96da0d/torchvision-0.23.0-cp313-cp313-win_amd64.whl", hash = "sha256:07d069cb29691ff566e3b7f11f20d91044f079e1dbdc9d72e0655899a9b06938", size = 1600749, upload-time = "2025-08-06T14:58:10.719Z" }, + { url = "https://files.pythonhosted.org/packages/05/35/72f91ad9ac7c19a849dedf083d347dc1123f0adeb401f53974f84f1d04c8/torchvision-0.23.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:2df618e1143805a7673aaf82cb5720dd9112d4e771983156aaf2ffff692eebf9", size = 2047192, upload-time = "2025-08-06T14:58:11.813Z" }, + { url = "https://files.pythonhosted.org/packages/1d/9d/406cea60a9eb9882145bcd62a184ee61e823e8e1d550cdc3c3ea866a9445/torchvision-0.23.0-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:2a3299d2b1d5a7aed2d3b6ffb69c672ca8830671967eb1cee1497bacd82fe47b", size = 2359295, upload-time = "2025-08-06T14:58:17.469Z" }, + { url = "https://files.pythonhosted.org/packages/2b/f4/34662f71a70fa1e59de99772142f22257ca750de05ccb400b8d2e3809c1d/torchvision-0.23.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:76bc4c0b63d5114aa81281390f8472a12a6a35ce9906e67ea6044e5af4cab60c", size = 8800474, upload-time = "2025-08-06T14:58:22.53Z" }, + { url = "https://files.pythonhosted.org/packages/6e/f5/b5a2d841a8d228b5dbda6d524704408e19e7ca6b7bb0f24490e081da1fa1/torchvision-0.23.0-cp313-cp313t-win_amd64.whl", hash = "sha256:b9e2dabf0da9c8aa9ea241afb63a8f3e98489e706b22ac3f30416a1be377153b", size = 1527667, upload-time = "2025-08-06T14:58:14.446Z" }, +] + +[[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 = "triton" +version = "3.4.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "setuptools", marker = "(platform_machine != 'aarch64' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != 'linux' and sys_platform != 'win32')" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/d0/66/b1eb52839f563623d185f0927eb3530ee4d5ffe9d377cdaf5346b306689e/triton-3.4.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:31c1d84a5c0ec2c0f8e8a072d7fd150cab84a9c239eaddc6706c081bfae4eb04", size = 155560068, upload-time = "2025-07-30T19:58:37.081Z" }, + { url = "https://files.pythonhosted.org/packages/30/7b/0a685684ed5322d2af0bddefed7906674f67974aa88b0fae6e82e3b766f6/triton-3.4.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:00be2964616f4c619193cb0d1b29a99bd4b001d7dc333816073f92cf2a8ccdeb", size = 155569223, upload-time = "2025-07-30T19:58:44.017Z" }, + { url = "https://files.pythonhosted.org/packages/20/63/8cb444ad5cdb25d999b7d647abac25af0ee37d292afc009940c05b82dda0/triton-3.4.0-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7936b18a3499ed62059414d7df563e6c163c5e16c3773678a3ee3d417865035d", size = 155659780, upload-time = "2025-07-30T19:58:51.171Z" }, +] + +[[package]] +name = "typer" +version = "0.16.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "rich" }, + { name = "shellingham" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/43/78/d90f616bf5f88f8710ad067c1f8705bf7618059836ca084e5bb2a0855d75/typer-0.16.1.tar.gz", hash = "sha256:d358c65a464a7a90f338e3bb7ff0c74ac081449e53884b12ba658cbd72990614", size = 102836, upload-time = "2025-08-18T19:18:22.898Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2d/76/06dbe78f39b2203d2a47d5facc5df5102d0561e2807396471b5f7c5a30a1/typer-0.16.1-py3-none-any.whl", hash = "sha256:90ee01cb02d9b8395ae21ee3368421faf21fa138cb2a541ed369c08cec5237c9", size = 46397, upload-time = "2025-08-18T19:18:21.663Z" }, +] + +[[package]] +name = "typing-extensions" +version = "4.14.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/98/5a/da40306b885cc8c09109dc2e1abd358d5684b1425678151cdaed4731c822/typing_extensions-4.14.1.tar.gz", hash = "sha256:38b39f4aeeab64884ce9f74c94263ef78f3c22467c8724005483154c26648d36", size = 107673, upload-time = "2025-07-04T13:28:34.16Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b5/00/d631e67a838026495268c2f6884f3711a15a9a2a96cd244fdaea53b823fb/typing_extensions-4.14.1-py3-none-any.whl", hash = "sha256:d1e1e3b58374dc93031d6eda2420a48ea44a36c2b4766a4fdeb3710755731d76", size = 43906, upload-time = "2025-07-04T13:28:32.743Z" }, +] + +[[package]] +name = "typing-inspection" +version = "0.4.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f8/b1/0c11f5058406b3af7609f121aaa6b609744687f1d158b3c3a5bf4cc94238/typing_inspection-0.4.1.tar.gz", hash = "sha256:6ae134cc0203c33377d43188d4064e9b357dba58cff3185f22924610e70a9d28", size = 75726, upload-time = "2025-05-21T18:55:23.885Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/17/69/cd203477f944c353c31bade965f880aa1061fd6bf05ded0726ca845b6ff7/typing_inspection-0.4.1-py3-none-any.whl", hash = "sha256:389055682238f53b04f7badcb49b989835495a96700ced5dab2d8feae4b26f51", size = 14552, upload-time = "2025-05-21T18:55:22.152Z" }, +] + +[[package]] +name = "tzdata" +version = "2025.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/95/32/1a225d6164441be760d75c2c42e2780dc0873fe382da3e98a2e1e48361e5/tzdata-2025.2.tar.gz", hash = "sha256:b60a638fcc0daffadf82fe0f57e53d06bdec2f36c4df66280ae79bce6bd6f2b9", size = 196380, upload-time = "2025-03-23T13:54:43.652Z" } +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 = "ultralytics" +version = "8.3.182" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "matplotlib" }, + { name = "numpy" }, + { name = "opencv-python" }, + { name = "pandas" }, + { name = "pillow" }, + { name = "psutil" }, + { name = "py-cpuinfo" }, + { name = "pyyaml" }, + { name = "requests" }, + { name = "scipy" }, + { name = "torch" }, + { name = "torchvision" }, + { name = "tqdm" }, + { name = "ultralytics-thop" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/3c/ba/664440b9314d3272be48c42b7da106ce82d86440c43337e9cf181ea3f87b/ultralytics-8.3.182.tar.gz", hash = "sha256:71e5ac25b57218de0f6125790c70f99d84e6ceacd59267261326491e53878dc8", size = 896610, upload-time = "2025-08-20T09:35:16.305Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1c/57/8b8c052fe627e95fd687512c9beb3db788c0adee69162fb9ba18aec12dc7/ultralytics-8.3.182-py3-none-any.whl", hash = "sha256:aa974136df49babc002442e278728253fe030052c797de73187dc6b1abb60437", size = 1044159, upload-time = "2025-08-20T09:35:13.567Z" }, +] + +[[package]] +name = "ultralytics-thop" +version = "2.0.16" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, + { name = "torch" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ab/b3/8e0a6bce52976250dd650b340aab6e80b6020e28bf2095d88636149fbcfe/ultralytics_thop-2.0.16.tar.gz", hash = "sha256:b09d8bdd2ced3d831e9905ed843e742298b967c8445aa56d960264a7f1209672", size = 34269, upload-time = "2025-08-20T12:07:26.56Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8b/04/45d2ee4c85d73c8d26ef360514f0ccd310956c4258858fe2306149901b9b/ultralytics_thop-2.0.16-py3-none-any.whl", hash = "sha256:772a71dfb706fe817bd662a6751a23693efa066938f58ea57772df14617818fe", size = 28727, upload-time = "2025-08-20T12:07:25.056Z" }, +] + +[[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 = "uvicorn" +version = "0.35.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "h11" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/5e/42/e0e305207bb88c6b8d3061399c6a961ffe5fbb7e2aa63c9234df7259e9cd/uvicorn-0.35.0.tar.gz", hash = "sha256:bc662f087f7cf2ce11a1d7fd70b90c9f98ef2e2831556dd078d131b96cc94a01", size = 78473, upload-time = "2025-06-28T16:15:46.058Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d2/e2/dc81b1bd1dcfe91735810265e9d26bc8ec5da45b4c0f6237e286819194c3/uvicorn-0.35.0-py3-none-any.whl", hash = "sha256:197535216b25ff9b785e29a0b79199f55222193d47f820816e7da751e9bc8d4a", size = 66406, upload-time = "2025-06-28T16:15:44.816Z" }, +]