Pausing for now, train loop should now work and adding some tests
This commit is contained in:
25
configs/debug_config.py
Normal file
25
configs/debug_config.py
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
from configs.base_config import base_config
|
||||||
|
|
||||||
|
# Create a debug configuration with minimal settings
|
||||||
|
config = base_config.copy()
|
||||||
|
|
||||||
|
# Update settings for quick debugging
|
||||||
|
config.update(
|
||||||
|
{
|
||||||
|
# Core configuration
|
||||||
|
"config_name": "debug_run",
|
||||||
|
"data_root": "data/PennFudanPed",
|
||||||
|
"num_classes": 2, # background + pedestrian
|
||||||
|
# Minimal training parameters
|
||||||
|
"batch_size": 1,
|
||||||
|
"num_epochs": 1, # Just one epoch for testing
|
||||||
|
"val_split_ratio": 0.2, # Use more validation samples for better testing coverage
|
||||||
|
# Performance optimizations
|
||||||
|
"pin_memory": False,
|
||||||
|
"num_workers": 0, # Use 0 workers to avoid multiprocessing complexities during debugging
|
||||||
|
# Logging settings
|
||||||
|
"log_freq": 1, # Log every batch for debugging
|
||||||
|
# Device setting - use CPU for reliable debugging
|
||||||
|
"device": "cpu", # Using CPU ensures consistent behavior across systems
|
||||||
|
}
|
||||||
|
)
|
||||||
@@ -5,6 +5,7 @@ description = "Add your description here"
|
|||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
requires-python = ">=3.12"
|
requires-python = ">=3.12"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"matplotlib>=3.10.1",
|
||||||
"numpy>=2.2.4",
|
"numpy>=2.2.4",
|
||||||
"pillow>=11.1.0",
|
"pillow>=11.1.0",
|
||||||
"pytest>=8.3.5",
|
"pytest>=8.3.5",
|
||||||
|
|||||||
152
scripts/test_model.py
Executable file
152
scripts/test_model.py
Executable file
@@ -0,0 +1,152 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
"""
|
||||||
|
Quick model testing script to verify model creation and inference.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import time
|
||||||
|
|
||||||
|
import torch
|
||||||
|
|
||||||
|
# Add project root to the path to enable imports
|
||||||
|
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
|
||||||
|
|
||||||
|
from models.detection import get_maskrcnn_model
|
||||||
|
from utils.data_utils import PennFudanDataset, get_transform
|
||||||
|
|
||||||
|
|
||||||
|
def test_model_creation():
|
||||||
|
"""Test that we can create the model."""
|
||||||
|
print("Testing model creation...")
|
||||||
|
model = get_maskrcnn_model(
|
||||||
|
num_classes=2, pretrained=False, pretrained_backbone=False
|
||||||
|
)
|
||||||
|
print("✓ Model created successfully")
|
||||||
|
return model
|
||||||
|
|
||||||
|
|
||||||
|
def test_model_forward(model, device):
|
||||||
|
"""Test model forward pass with random inputs."""
|
||||||
|
print("\nTesting model forward pass...")
|
||||||
|
|
||||||
|
# Create a random batch
|
||||||
|
image = torch.rand(3, 300, 400, device=device) # Random image
|
||||||
|
|
||||||
|
# Create a random target
|
||||||
|
target = {
|
||||||
|
"boxes": torch.tensor(
|
||||||
|
[[100, 100, 200, 200]], dtype=torch.float32, device=device
|
||||||
|
),
|
||||||
|
"labels": torch.tensor([1], dtype=torch.int64, device=device),
|
||||||
|
"masks": torch.randint(0, 2, (1, 300, 400), dtype=torch.uint8, device=device),
|
||||||
|
"image_id": torch.tensor([0], device=device),
|
||||||
|
"area": torch.tensor([10000.0], dtype=torch.float32, device=device),
|
||||||
|
"iscrowd": torch.tensor([0], dtype=torch.uint8, device=device),
|
||||||
|
}
|
||||||
|
|
||||||
|
# Test inference mode (no targets)
|
||||||
|
model.eval()
|
||||||
|
with torch.no_grad():
|
||||||
|
start_time = time.time()
|
||||||
|
output_inference = model([image])
|
||||||
|
inference_time = time.time() - start_time
|
||||||
|
|
||||||
|
# Verify inference output
|
||||||
|
print(f"✓ Inference mode output: {type(output_inference)}")
|
||||||
|
print(f"✓ Inference time: {inference_time:.3f}s")
|
||||||
|
print(f"✓ Detection boxes shape: {output_inference[0]['boxes'].shape}")
|
||||||
|
print(f"✓ Detection scores shape: {output_inference[0]['scores'].shape}")
|
||||||
|
|
||||||
|
# Test training mode (with targets)
|
||||||
|
model.train()
|
||||||
|
start_time = time.time()
|
||||||
|
output_train = model([image], [target])
|
||||||
|
train_time = time.time() - start_time
|
||||||
|
|
||||||
|
# Verify training output
|
||||||
|
print(f"✓ Training mode output: {type(output_train)}")
|
||||||
|
print(f"✓ Training time: {train_time:.3f}s")
|
||||||
|
|
||||||
|
# Print loss values
|
||||||
|
for loss_name, loss_value in output_train.items():
|
||||||
|
print(f"✓ {loss_name}: {loss_value.item():.4f}")
|
||||||
|
|
||||||
|
return output_train
|
||||||
|
|
||||||
|
|
||||||
|
def test_model_backward(model, loss_dict, device):
|
||||||
|
"""Test model backward pass."""
|
||||||
|
print("\nTesting model backward pass...")
|
||||||
|
|
||||||
|
# Calculate total loss
|
||||||
|
total_loss = sum(loss for loss in loss_dict.values())
|
||||||
|
|
||||||
|
# Create optimizer
|
||||||
|
optimizer = torch.optim.SGD(model.parameters(), lr=0.001)
|
||||||
|
|
||||||
|
# Backward pass
|
||||||
|
start_time = time.time()
|
||||||
|
optimizer.zero_grad()
|
||||||
|
total_loss.backward()
|
||||||
|
optimizer.step()
|
||||||
|
backward_time = time.time() - start_time
|
||||||
|
|
||||||
|
print("✓ Backward pass and optimization completed")
|
||||||
|
print(f"✓ Backward time: {backward_time:.3f}s")
|
||||||
|
|
||||||
|
# Check that gradients were calculated
|
||||||
|
has_gradients = any(
|
||||||
|
param.grad is not None for param in model.parameters() if param.requires_grad
|
||||||
|
)
|
||||||
|
print(f"✓ Model has gradients: {has_gradients}")
|
||||||
|
|
||||||
|
|
||||||
|
def test_dataset():
|
||||||
|
"""Test that we can load the dataset."""
|
||||||
|
print("\nTesting dataset loading...")
|
||||||
|
|
||||||
|
data_root = "data/PennFudanPed"
|
||||||
|
if not os.path.exists(data_root):
|
||||||
|
print("✗ Dataset not found at", data_root)
|
||||||
|
return None
|
||||||
|
|
||||||
|
# Create dataset
|
||||||
|
dataset = PennFudanDataset(root=data_root, transforms=get_transform(train=True))
|
||||||
|
print(f"✓ Dataset loaded with {len(dataset)} samples")
|
||||||
|
|
||||||
|
# Test loading a sample
|
||||||
|
start_time = time.time()
|
||||||
|
img, target = dataset[0]
|
||||||
|
load_time = time.time() - start_time
|
||||||
|
|
||||||
|
print(f"✓ Sample loaded in {load_time:.3f}s")
|
||||||
|
print(f"✓ Image shape: {img.shape}")
|
||||||
|
print(f"✓ Target boxes shape: {target['boxes'].shape}")
|
||||||
|
|
||||||
|
return dataset
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
"""Run all tests."""
|
||||||
|
print("=== Quick Model Testing Script ===")
|
||||||
|
|
||||||
|
# Set device
|
||||||
|
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
|
||||||
|
print(f"Using device: {device}")
|
||||||
|
|
||||||
|
# Run tests
|
||||||
|
model = test_model_creation()
|
||||||
|
model.to(device)
|
||||||
|
|
||||||
|
loss_dict = test_model_forward(model, device)
|
||||||
|
|
||||||
|
test_model_backward(model, loss_dict, device)
|
||||||
|
|
||||||
|
test_dataset()
|
||||||
|
|
||||||
|
print("\n=== All tests completed successfully ===")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
175
scripts/visualize_predictions.py
Executable file
175
scripts/visualize_predictions.py
Executable file
@@ -0,0 +1,175 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
"""
|
||||||
|
Visualization script for model predictions on the Penn-Fudan dataset.
|
||||||
|
This helps visualize and debug model predictions.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
import matplotlib.pyplot as plt
|
||||||
|
import numpy as np
|
||||||
|
import torch
|
||||||
|
|
||||||
|
# Add project root to path for imports
|
||||||
|
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
|
||||||
|
|
||||||
|
from models.detection import get_maskrcnn_model
|
||||||
|
from utils.common import load_checkpoint, load_config
|
||||||
|
from utils.data_utils import PennFudanDataset, get_transform
|
||||||
|
|
||||||
|
|
||||||
|
def visualize_prediction(image, prediction, threshold=0.5):
|
||||||
|
"""
|
||||||
|
Visualize model prediction on an image.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
image (torch.Tensor): The input image [C, H, W]
|
||||||
|
prediction (dict): Model prediction dict with boxes, scores, labels, masks
|
||||||
|
threshold (float): Score threshold for visualization
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
plt.Figure: The matplotlib figure with the visualization
|
||||||
|
"""
|
||||||
|
# Convert image from tensor to numpy
|
||||||
|
img_np = image.permute(1, 2, 0).cpu().numpy()
|
||||||
|
|
||||||
|
# Denormalize if needed
|
||||||
|
if img_np.max() <= 1.0:
|
||||||
|
img_np = (img_np * 255).astype(np.uint8)
|
||||||
|
|
||||||
|
# Create figure and axes
|
||||||
|
fig, ax = plt.subplots(1, 1, figsize=(12, 9))
|
||||||
|
ax.imshow(img_np)
|
||||||
|
ax.set_title("Model Predictions")
|
||||||
|
|
||||||
|
# Get predictions
|
||||||
|
boxes = prediction["boxes"].cpu().numpy()
|
||||||
|
scores = prediction["scores"].cpu().numpy()
|
||||||
|
labels = prediction["labels"].cpu().numpy()
|
||||||
|
masks = prediction["masks"].cpu().numpy()
|
||||||
|
|
||||||
|
# Filter by threshold
|
||||||
|
mask = scores >= threshold
|
||||||
|
boxes = boxes[mask]
|
||||||
|
scores = scores[mask]
|
||||||
|
labels = labels[mask]
|
||||||
|
masks = masks[mask]
|
||||||
|
|
||||||
|
# Draw predictions
|
||||||
|
for box, score, label, mask in zip(boxes, scores, labels, masks):
|
||||||
|
# Draw box
|
||||||
|
x1, y1, x2, y2 = box
|
||||||
|
rect = plt.Rectangle(
|
||||||
|
(x1, y1), x2 - x1, y2 - y1, fill=False, edgecolor="red", linewidth=2
|
||||||
|
)
|
||||||
|
ax.add_patch(rect)
|
||||||
|
|
||||||
|
# Add label and score
|
||||||
|
ax.text(
|
||||||
|
x1, y1, f"Person: {score:.2f}", bbox=dict(facecolor="yellow", alpha=0.5)
|
||||||
|
)
|
||||||
|
|
||||||
|
# Draw mask (with transparency)
|
||||||
|
mask = mask[0] > 0.5 # Threshold mask
|
||||||
|
mask_color = np.zeros((mask.shape[0], mask.shape[1], 3), dtype=np.uint8)
|
||||||
|
mask_color[mask] = [255, 0, 0] # Red color
|
||||||
|
ax.imshow(mask_color, alpha=0.3)
|
||||||
|
|
||||||
|
# Show count of detections
|
||||||
|
ax.set_xlabel(f"Found {len(boxes)} pedestrians with confidence >= {threshold}")
|
||||||
|
|
||||||
|
plt.tight_layout()
|
||||||
|
return fig
|
||||||
|
|
||||||
|
|
||||||
|
def run_inference(model, dataset, device, idx=0):
|
||||||
|
"""
|
||||||
|
Run inference on a single image from the dataset.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
model (torch.nn.Module): The model
|
||||||
|
dataset (PennFudanDataset): The dataset
|
||||||
|
device (torch.device): The device
|
||||||
|
idx (int): Index of the image to test
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
tuple: (image, prediction)
|
||||||
|
"""
|
||||||
|
# Get image
|
||||||
|
image, _ = dataset[idx]
|
||||||
|
|
||||||
|
# Prepare for model
|
||||||
|
image = image.to(device)
|
||||||
|
|
||||||
|
# Run inference
|
||||||
|
model.eval()
|
||||||
|
with torch.no_grad():
|
||||||
|
prediction = model([image])[0]
|
||||||
|
|
||||||
|
return image, prediction
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
"""Main entry point."""
|
||||||
|
parser = argparse.ArgumentParser(description="Visualize model predictions")
|
||||||
|
parser.add_argument("--config", required=True, help="Path to config file")
|
||||||
|
parser.add_argument("--checkpoint", required=True, help="Path to checkpoint file")
|
||||||
|
parser.add_argument("--index", type=int, default=0, help="Image index to visualize")
|
||||||
|
parser.add_argument("--threshold", type=float, default=0.5, help="Score threshold")
|
||||||
|
parser.add_argument("--output", help="Path to save visualization image")
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
# Load config
|
||||||
|
config = load_config(args.config)
|
||||||
|
|
||||||
|
# Setup device
|
||||||
|
device = torch.device(config.get("device", "cpu"))
|
||||||
|
print(f"Using device: {device}")
|
||||||
|
|
||||||
|
# Create model
|
||||||
|
model = get_maskrcnn_model(
|
||||||
|
num_classes=config.get("num_classes", 2),
|
||||||
|
pretrained=False,
|
||||||
|
pretrained_backbone=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Load checkpoint
|
||||||
|
checkpoint, _ = load_checkpoint(args.checkpoint, model, device)
|
||||||
|
model.to(device)
|
||||||
|
print(f"Loaded checkpoint from: {args.checkpoint}")
|
||||||
|
|
||||||
|
# Create dataset
|
||||||
|
data_root = config.get("data_root", "data/PennFudanPed")
|
||||||
|
if not os.path.exists(data_root):
|
||||||
|
print(f"Error: Data not found at {data_root}")
|
||||||
|
return
|
||||||
|
|
||||||
|
dataset = PennFudanDataset(root=data_root, transforms=get_transform(train=False))
|
||||||
|
print(f"Dataset loaded with {len(dataset)} images")
|
||||||
|
|
||||||
|
# Validate index
|
||||||
|
if args.index < 0 or args.index >= len(dataset):
|
||||||
|
print(f"Error: Index {args.index} out of range (0-{len(dataset)-1})")
|
||||||
|
return
|
||||||
|
|
||||||
|
# Run inference
|
||||||
|
print(f"Running inference on image {args.index}...")
|
||||||
|
image, prediction = run_inference(model, dataset, device, args.index)
|
||||||
|
|
||||||
|
# Visualize prediction
|
||||||
|
print("Visualizing predictions...")
|
||||||
|
fig = visualize_prediction(image, prediction, threshold=args.threshold)
|
||||||
|
|
||||||
|
# Save or show
|
||||||
|
if args.output:
|
||||||
|
fig.savefig(args.output)
|
||||||
|
print(f"Visualization saved to: {args.output}")
|
||||||
|
else:
|
||||||
|
plt.show()
|
||||||
|
print("Visualization displayed. Close window to continue.")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
BIN
test_prediction.png
Normal file
BIN
test_prediction.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 944 KiB |
@@ -0,0 +1,56 @@
|
|||||||
|
import os
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
import torch
|
||||||
|
|
||||||
|
# Add project root to the path to enable imports
|
||||||
|
project_root = Path(__file__).parent.parent
|
||||||
|
sys.path.insert(0, str(project_root))
|
||||||
|
|
||||||
|
from models.detection import get_maskrcnn_model # noqa: E402
|
||||||
|
from utils.data_utils import PennFudanDataset, get_transform # noqa: E402
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def device():
|
||||||
|
"""Return CPU device for consistent testing."""
|
||||||
|
return torch.device("cpu")
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def test_config():
|
||||||
|
"""Return a minimal config dictionary for testing."""
|
||||||
|
return {
|
||||||
|
"data_root": "data/PennFudanPed",
|
||||||
|
"num_classes": 2,
|
||||||
|
"batch_size": 1,
|
||||||
|
"device": "cpu",
|
||||||
|
"output_dir": "test_outputs",
|
||||||
|
"config_name": "test_run",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def small_model(device):
|
||||||
|
"""Return a small Mask R-CNN model for testing."""
|
||||||
|
model = get_maskrcnn_model(
|
||||||
|
num_classes=2, pretrained=False, pretrained_backbone=False
|
||||||
|
)
|
||||||
|
model.to(device)
|
||||||
|
return model
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def sample_dataset():
|
||||||
|
"""Return a small dataset for testing if available."""
|
||||||
|
data_root = "data/PennFudanPed"
|
||||||
|
|
||||||
|
# Skip if data is not available
|
||||||
|
if not os.path.exists(data_root):
|
||||||
|
pytest.skip("Test dataset not available")
|
||||||
|
|
||||||
|
transforms = get_transform(train=False)
|
||||||
|
dataset = PennFudanDataset(root=data_root, transforms=transforms)
|
||||||
|
return dataset
|
||||||
|
|||||||
108
tests/test_data_utils.py
Normal file
108
tests/test_data_utils.py
Normal file
@@ -0,0 +1,108 @@
|
|||||||
|
import torch
|
||||||
|
|
||||||
|
from utils.data_utils import collate_fn, get_transform
|
||||||
|
|
||||||
|
|
||||||
|
def test_dataset_len(sample_dataset):
|
||||||
|
"""Test that the dataset has the expected length."""
|
||||||
|
# PennFudanPed has 170 images
|
||||||
|
assert len(sample_dataset) > 0, "Dataset should not be empty"
|
||||||
|
|
||||||
|
|
||||||
|
def test_dataset_getitem(sample_dataset):
|
||||||
|
"""Test that __getitem__ returns expected format."""
|
||||||
|
if len(sample_dataset) == 0:
|
||||||
|
return # Skip if no data
|
||||||
|
|
||||||
|
# Get first item
|
||||||
|
img, target = sample_dataset[0]
|
||||||
|
|
||||||
|
# Check image
|
||||||
|
assert isinstance(img, torch.Tensor), "Image should be a tensor"
|
||||||
|
assert img.dim() == 3, "Image should have 3 dimensions (C, H, W)"
|
||||||
|
assert img.shape[0] == 3, "Image should have 3 channels (RGB)"
|
||||||
|
|
||||||
|
# Check target
|
||||||
|
assert isinstance(target, dict), "Target should be a dictionary"
|
||||||
|
assert "boxes" in target, "Target should contain 'boxes'"
|
||||||
|
assert "labels" in target, "Target should contain 'labels'"
|
||||||
|
assert "masks" in target, "Target should contain 'masks'"
|
||||||
|
assert "image_id" in target, "Target should contain 'image_id'"
|
||||||
|
assert "area" in target, "Target should contain 'area'"
|
||||||
|
assert "iscrowd" in target, "Target should contain 'iscrowd'"
|
||||||
|
|
||||||
|
# Check target values
|
||||||
|
assert (
|
||||||
|
target["boxes"].shape[1] == 4
|
||||||
|
), "Boxes should have 4 coordinates (x1, y1, x2, y2)"
|
||||||
|
assert target["labels"].dim() == 1, "Labels should be a 1D tensor"
|
||||||
|
assert target["masks"].dim() == 3, "Masks should be a 3D tensor (N, H, W)"
|
||||||
|
|
||||||
|
|
||||||
|
def test_transforms(sample_dataset):
|
||||||
|
"""Test that transforms are applied correctly."""
|
||||||
|
if len(sample_dataset) == 0:
|
||||||
|
return # Skip if no data
|
||||||
|
|
||||||
|
# Get original transform
|
||||||
|
orig_transforms = sample_dataset.transforms
|
||||||
|
|
||||||
|
# Apply different transforms
|
||||||
|
train_transforms = get_transform(train=True)
|
||||||
|
eval_transforms = get_transform(train=False)
|
||||||
|
|
||||||
|
# Test that we can switch transforms
|
||||||
|
sample_dataset.transforms = train_transforms
|
||||||
|
img_train, target_train = sample_dataset[0]
|
||||||
|
|
||||||
|
sample_dataset.transforms = eval_transforms
|
||||||
|
img_eval, target_eval = sample_dataset[0]
|
||||||
|
|
||||||
|
# Restore original transforms
|
||||||
|
sample_dataset.transforms = orig_transforms
|
||||||
|
|
||||||
|
# Images should be tensors with expected properties
|
||||||
|
assert img_train.dim() == img_eval.dim() == 3
|
||||||
|
assert img_train.shape[0] == img_eval.shape[0] == 3
|
||||||
|
|
||||||
|
|
||||||
|
def test_collate_fn():
|
||||||
|
"""Test the collate function."""
|
||||||
|
# Create dummy batch data
|
||||||
|
dummy_img1 = torch.rand(3, 100, 100)
|
||||||
|
dummy_img2 = torch.rand(3, 100, 100)
|
||||||
|
|
||||||
|
dummy_target1 = {
|
||||||
|
"boxes": torch.tensor([[10, 10, 50, 50]], dtype=torch.float32),
|
||||||
|
"labels": torch.tensor([1], dtype=torch.int64),
|
||||||
|
"masks": torch.zeros(1, 100, 100, dtype=torch.uint8),
|
||||||
|
"image_id": torch.tensor([0]),
|
||||||
|
"area": torch.tensor([1600.0], dtype=torch.float32),
|
||||||
|
"iscrowd": torch.tensor([0], dtype=torch.uint8),
|
||||||
|
}
|
||||||
|
|
||||||
|
dummy_target2 = {
|
||||||
|
"boxes": torch.tensor([[20, 20, 60, 60]], dtype=torch.float32),
|
||||||
|
"labels": torch.tensor([1], dtype=torch.int64),
|
||||||
|
"masks": torch.zeros(1, 100, 100, dtype=torch.uint8),
|
||||||
|
"image_id": torch.tensor([1]),
|
||||||
|
"area": torch.tensor([1600.0], dtype=torch.float32),
|
||||||
|
"iscrowd": torch.tensor([0], dtype=torch.uint8),
|
||||||
|
}
|
||||||
|
|
||||||
|
batch = [(dummy_img1, dummy_target1), (dummy_img2, dummy_target2)]
|
||||||
|
|
||||||
|
# Apply collate_fn
|
||||||
|
images, targets = collate_fn(batch)
|
||||||
|
|
||||||
|
# Check results
|
||||||
|
assert len(images) == 2, "Should have 2 images"
|
||||||
|
assert len(targets) == 2, "Should have 2 targets"
|
||||||
|
assert torch.allclose(images[0], dummy_img1), "First image should match"
|
||||||
|
assert torch.allclose(images[1], dummy_img2), "Second image should match"
|
||||||
|
assert torch.allclose(
|
||||||
|
targets[0]["boxes"], dummy_target1["boxes"]
|
||||||
|
), "First boxes should match"
|
||||||
|
assert torch.allclose(
|
||||||
|
targets[1]["boxes"], dummy_target2["boxes"]
|
||||||
|
), "Second boxes should match"
|
||||||
102
tests/test_model.py
Normal file
102
tests/test_model.py
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
import torch
|
||||||
|
import torchvision
|
||||||
|
|
||||||
|
from utils.eval_utils import evaluate
|
||||||
|
|
||||||
|
|
||||||
|
def test_model_creation(small_model):
|
||||||
|
"""Test that the model is created correctly."""
|
||||||
|
assert isinstance(small_model, torchvision.models.detection.MaskRCNN)
|
||||||
|
assert small_model.roi_heads.box_predictor.cls_score.out_features == 2
|
||||||
|
assert small_model.roi_heads.mask_predictor.mask_fcn_logits.out_channels == 2
|
||||||
|
|
||||||
|
|
||||||
|
def test_model_forward_train_mode(small_model, sample_dataset, device):
|
||||||
|
"""Test model forward pass in training mode."""
|
||||||
|
if len(sample_dataset) == 0:
|
||||||
|
return # Skip if no data
|
||||||
|
|
||||||
|
# Set model to training mode
|
||||||
|
small_model.train()
|
||||||
|
|
||||||
|
# Get a batch
|
||||||
|
img, target = sample_dataset[0]
|
||||||
|
img = img.to(device)
|
||||||
|
target = {k: v.to(device) for k, v in target.items()}
|
||||||
|
|
||||||
|
# Forward pass with targets should return loss dict in training mode
|
||||||
|
loss_dict = small_model([img], [target])
|
||||||
|
|
||||||
|
# Verify loss dict structure
|
||||||
|
assert isinstance(loss_dict, dict), "Loss should be a dictionary"
|
||||||
|
assert "loss_classifier" in loss_dict, "Should have classifier loss"
|
||||||
|
assert "loss_box_reg" in loss_dict, "Should have box regression loss"
|
||||||
|
assert "loss_mask" in loss_dict, "Should have mask loss"
|
||||||
|
assert "loss_objectness" in loss_dict, "Should have objectness loss"
|
||||||
|
assert "loss_rpn_box_reg" in loss_dict, "Should have RPN box regression loss"
|
||||||
|
|
||||||
|
# Verify loss values
|
||||||
|
for loss_name, loss_value in loss_dict.items():
|
||||||
|
assert isinstance(loss_value, torch.Tensor), f"{loss_name} should be a tensor"
|
||||||
|
assert loss_value.dim() == 0, f"{loss_name} should be a scalar tensor"
|
||||||
|
assert not torch.isnan(loss_value), f"{loss_name} should not be NaN"
|
||||||
|
assert not torch.isinf(loss_value), f"{loss_name} should not be infinite"
|
||||||
|
|
||||||
|
|
||||||
|
def test_model_forward_eval_mode(small_model, sample_dataset, device):
|
||||||
|
"""Test model forward pass in evaluation mode."""
|
||||||
|
if len(sample_dataset) == 0:
|
||||||
|
return # Skip if no data
|
||||||
|
|
||||||
|
# Set model to evaluation mode
|
||||||
|
small_model.eval()
|
||||||
|
|
||||||
|
# Get a batch
|
||||||
|
img, target = sample_dataset[0]
|
||||||
|
img = img.to(device)
|
||||||
|
|
||||||
|
# Forward pass without targets should return predictions in eval mode
|
||||||
|
with torch.no_grad():
|
||||||
|
predictions = small_model([img])
|
||||||
|
|
||||||
|
# Verify predictions structure
|
||||||
|
assert isinstance(predictions, list), "Predictions should be a list"
|
||||||
|
assert len(predictions) == 1, "Should have predictions for 1 image"
|
||||||
|
|
||||||
|
pred = predictions[0]
|
||||||
|
assert "boxes" in pred, "Predictions should contain 'boxes'"
|
||||||
|
assert "scores" in pred, "Predictions should contain 'scores'"
|
||||||
|
assert "labels" in pred, "Predictions should contain 'labels'"
|
||||||
|
assert "masks" in pred, "Predictions should contain 'masks'"
|
||||||
|
|
||||||
|
|
||||||
|
def test_evaluate_function(small_model, sample_dataset, device):
|
||||||
|
"""Test the evaluate function."""
|
||||||
|
if len(sample_dataset) == 0:
|
||||||
|
return # Skip if no data
|
||||||
|
|
||||||
|
# Create a tiny dataloader for testing
|
||||||
|
from torch.utils.data import DataLoader
|
||||||
|
|
||||||
|
from utils.data_utils import collate_fn
|
||||||
|
|
||||||
|
# Use only 2 samples for quick testing
|
||||||
|
small_ds = torch.utils.data.Subset(
|
||||||
|
sample_dataset, range(min(2, len(sample_dataset)))
|
||||||
|
)
|
||||||
|
dataloader = DataLoader(
|
||||||
|
small_ds, batch_size=1, shuffle=False, collate_fn=collate_fn
|
||||||
|
)
|
||||||
|
|
||||||
|
# Set model to eval mode
|
||||||
|
small_model.eval()
|
||||||
|
|
||||||
|
# Import evaluate function
|
||||||
|
|
||||||
|
# Run evaluation
|
||||||
|
metrics = evaluate(small_model, dataloader, device)
|
||||||
|
|
||||||
|
# Check results
|
||||||
|
assert isinstance(metrics, dict), "Metrics should be a dictionary"
|
||||||
|
assert "average_loss" in metrics, "Metrics should contain 'average_loss'"
|
||||||
|
assert metrics["average_loss"] >= 0, "Loss should be non-negative"
|
||||||
77
tests/test_visualization.py
Normal file
77
tests/test_visualization.py
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
import matplotlib.pyplot as plt
|
||||||
|
import torch
|
||||||
|
|
||||||
|
# Import visualization functions
|
||||||
|
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
|
||||||
|
from scripts.visualize_predictions import visualize_prediction
|
||||||
|
|
||||||
|
|
||||||
|
def test_visualize_prediction():
|
||||||
|
"""Test that the visualization function works."""
|
||||||
|
# Create a dummy image tensor
|
||||||
|
image = torch.rand(3, 400, 600)
|
||||||
|
|
||||||
|
# Create a dummy prediction dictionary
|
||||||
|
prediction = {
|
||||||
|
"boxes": torch.tensor(
|
||||||
|
[[100, 100, 200, 200], [300, 300, 400, 400]], dtype=torch.float32
|
||||||
|
),
|
||||||
|
"scores": torch.tensor([0.9, 0.7], dtype=torch.float32),
|
||||||
|
"labels": torch.tensor([1, 1], dtype=torch.int64),
|
||||||
|
"masks": torch.zeros(2, 1, 400, 600, dtype=torch.float32),
|
||||||
|
}
|
||||||
|
|
||||||
|
# Set some pixels in the mask to 1
|
||||||
|
prediction["masks"][0, 0, 100:200, 100:200] = 1.0
|
||||||
|
prediction["masks"][1, 0, 300:400, 300:400] = 1.0
|
||||||
|
|
||||||
|
# Call the visualization function
|
||||||
|
fig = visualize_prediction(image, prediction, threshold=0.5)
|
||||||
|
|
||||||
|
# Check that a figure was returned
|
||||||
|
assert isinstance(fig, plt.Figure)
|
||||||
|
|
||||||
|
# Check figure properties
|
||||||
|
assert len(fig.axes) == 1
|
||||||
|
|
||||||
|
# Close the figure to avoid memory leaks
|
||||||
|
plt.close(fig)
|
||||||
|
|
||||||
|
|
||||||
|
def test_visualize_prediction_threshold():
|
||||||
|
"""Test that the threshold parameter filters predictions correctly."""
|
||||||
|
# Create a dummy image tensor
|
||||||
|
image = torch.rand(3, 400, 600)
|
||||||
|
|
||||||
|
# Create a dummy prediction dictionary with varying scores
|
||||||
|
prediction = {
|
||||||
|
"boxes": torch.tensor(
|
||||||
|
[[100, 100, 200, 200], [300, 300, 400, 400], [500, 100, 550, 150]],
|
||||||
|
dtype=torch.float32,
|
||||||
|
),
|
||||||
|
"scores": torch.tensor([0.9, 0.7, 0.3], dtype=torch.float32),
|
||||||
|
"labels": torch.tensor([1, 1, 1], dtype=torch.int64),
|
||||||
|
"masks": torch.zeros(3, 1, 400, 600, dtype=torch.float32),
|
||||||
|
}
|
||||||
|
|
||||||
|
# Call the visualization function with different thresholds
|
||||||
|
fig_low = visualize_prediction(image, prediction, threshold=0.2)
|
||||||
|
fig_med = visualize_prediction(image, prediction, threshold=0.5)
|
||||||
|
fig_high = visualize_prediction(image, prediction, threshold=0.8)
|
||||||
|
|
||||||
|
# Low threshold should show all 3 boxes
|
||||||
|
assert "Found 3" in fig_low.axes[0].get_xlabel()
|
||||||
|
|
||||||
|
# Medium threshold should show 2 boxes
|
||||||
|
assert "Found 2" in fig_med.axes[0].get_xlabel()
|
||||||
|
|
||||||
|
# High threshold should show 1 box
|
||||||
|
assert "Found 1" in fig_high.axes[0].get_xlabel()
|
||||||
|
|
||||||
|
# Close figures
|
||||||
|
plt.close(fig_low)
|
||||||
|
plt.close(fig_med)
|
||||||
|
plt.close(fig_high)
|
||||||
2
train.py
2
train.py
@@ -310,7 +310,7 @@ def main(args):
|
|||||||
"num_epochs", 10
|
"num_epochs", 10
|
||||||
) - 1:
|
) - 1:
|
||||||
checkpoint_file = os.path.join(
|
checkpoint_file = os.path.join(
|
||||||
checkpoint_path, f"checkpoint_epoch_{epoch + 1}.pth"
|
checkpoint_path, f"checkpoint_epoch_{epoch+1}.pth"
|
||||||
)
|
)
|
||||||
checkpoint = {
|
checkpoint = {
|
||||||
"epoch": epoch + 1,
|
"epoch": epoch + 1,
|
||||||
|
|||||||
@@ -18,80 +18,91 @@ class PennFudanDataset(torch.utils.data.Dataset):
|
|||||||
self.masks = sorted(list(os.listdir(os.path.join(root, "PedMasks"))))
|
self.masks = sorted(list(os.listdir(os.path.join(root, "PedMasks"))))
|
||||||
|
|
||||||
def __getitem__(self, idx):
|
def __getitem__(self, idx):
|
||||||
# Load images and masks
|
"""Get a sample from the dataset.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
idx (int): Index of the sample to retrieve.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
tuple: (image, target) where target is a dictionary containing various object annotations.
|
||||||
|
"""
|
||||||
|
# Load image
|
||||||
img_path = os.path.join(self.root, "PNGImages", self.imgs[idx])
|
img_path = os.path.join(self.root, "PNGImages", self.imgs[idx])
|
||||||
mask_path = os.path.join(self.root, "PedMasks", self.masks[idx])
|
mask_path = os.path.join(self.root, "PedMasks", self.masks[idx])
|
||||||
|
|
||||||
|
# Use PIL to load images (more memory efficient)
|
||||||
img = Image.open(img_path).convert("RGB")
|
img = Image.open(img_path).convert("RGB")
|
||||||
# Note: Masks are not converted to RGB, contains index values
|
|
||||||
mask = Image.open(mask_path)
|
mask = Image.open(mask_path)
|
||||||
|
|
||||||
# Convert mask to numpy array
|
# Convert mask PIL image to numpy array
|
||||||
mask = np.array(mask)
|
mask = np.array(mask)
|
||||||
# Instances are encoded as different colors
|
|
||||||
|
# Find all object instances (each instance has a unique value in the mask)
|
||||||
|
# Value 0 is the background
|
||||||
obj_ids = np.unique(mask)
|
obj_ids = np.unique(mask)
|
||||||
# First id is the background, so remove it
|
obj_ids = obj_ids[1:] # Remove background (id=0)
|
||||||
obj_ids = obj_ids[1:]
|
|
||||||
|
|
||||||
# Split the color-encoded mask into a set of binary masks
|
# Split the mask into binary masks for each object instance
|
||||||
binary_masks = mask == obj_ids[:, None, None]
|
masks = mask == obj_ids[:, None, None]
|
||||||
|
|
||||||
# Get bounding box coordinates for each mask
|
# Get bounding box for each mask
|
||||||
num_objs = len(obj_ids)
|
num_objs = len(obj_ids)
|
||||||
boxes = []
|
boxes = []
|
||||||
|
|
||||||
for i in range(num_objs):
|
for i in range(num_objs):
|
||||||
pos = np.where(binary_masks[i])
|
pos = np.where(masks[i])
|
||||||
|
if len(pos[0]) == 0 or len(pos[1]) == 0: # Skip empty masks
|
||||||
|
continue
|
||||||
|
|
||||||
xmin = np.min(pos[1])
|
xmin = np.min(pos[1])
|
||||||
xmax = np.max(pos[1])
|
xmax = np.max(pos[1])
|
||||||
ymin = np.min(pos[0])
|
ymin = np.min(pos[0])
|
||||||
ymax = np.max(pos[0])
|
ymax = np.max(pos[0])
|
||||||
# Filter out potentially empty masks or masks with zero area
|
|
||||||
if xmax > xmin and ymax > ymin:
|
|
||||||
boxes.append([xmin, ymin, xmax, ymax])
|
|
||||||
else:
|
|
||||||
# If box is invalid, we might need to handle this
|
|
||||||
# For now, let's remove the corresponding mask as well
|
|
||||||
# This requires careful index handling if filtering occurs
|
|
||||||
# A safer approach might be to filter masks *after* box generation
|
|
||||||
# Let's recalculate binary_masks based on valid boxes later if needed
|
|
||||||
pass # placeholder for potential filtering logic
|
|
||||||
|
|
||||||
# Ensure boxes list isn't empty if filtering happened
|
# Skip boxes with zero area
|
||||||
if not boxes:
|
if xmax <= xmin or ymax <= ymin:
|
||||||
# Handle case with no valid boxes found - return dummy target? Or raise error?
|
continue
|
||||||
# For now, let's create dummy tensors. This should be revisited.
|
|
||||||
print(
|
boxes.append([xmin, ymin, xmax, ymax])
|
||||||
f"Warning: No valid boxes found for image {idx}. Returning dummy target."
|
|
||||||
)
|
# Convert everything to tensors
|
||||||
boxes = torch.zeros((0, 4), dtype=torch.float32)
|
if boxes:
|
||||||
labels = torch.zeros((0,), dtype=torch.int64)
|
|
||||||
binary_masks = torch.zeros(
|
|
||||||
(0, mask.shape[0], mask.shape[1]), dtype=torch.uint8
|
|
||||||
)
|
|
||||||
image_id = torch.tensor([idx])
|
|
||||||
area = torch.zeros((0,), dtype=torch.float32)
|
|
||||||
iscrowd = torch.zeros((0,), dtype=torch.uint8)
|
|
||||||
else:
|
|
||||||
boxes = torch.as_tensor(boxes, dtype=torch.float32)
|
boxes = torch.as_tensor(boxes, dtype=torch.float32)
|
||||||
# There is only one class (pedestrian)
|
labels = torch.ones(
|
||||||
labels = torch.ones((num_objs,), dtype=torch.int64)
|
(len(boxes),), dtype=torch.int64
|
||||||
binary_masks = torch.as_tensor(binary_masks, dtype=torch.uint8)
|
) # All objects are pedestrians (class 1)
|
||||||
image_id = torch.tensor([idx])
|
masks = torch.as_tensor(masks, dtype=torch.uint8)
|
||||||
# Calculate area
|
|
||||||
|
# Calculate area of each box
|
||||||
area = (boxes[:, 3] - boxes[:, 1]) * (boxes[:, 2] - boxes[:, 0])
|
area = (boxes[:, 3] - boxes[:, 1]) * (boxes[:, 2] - boxes[:, 0])
|
||||||
# Assume all instances are not crowd
|
|
||||||
iscrowd = torch.zeros((num_objs,), dtype=torch.uint8)
|
|
||||||
|
|
||||||
target = {}
|
# All instances are not crowd
|
||||||
target["boxes"] = boxes
|
iscrowd = torch.zeros((len(boxes),), dtype=torch.uint8)
|
||||||
target["labels"] = labels
|
|
||||||
target["masks"] = binary_masks
|
|
||||||
target["image_id"] = image_id
|
|
||||||
target["area"] = area
|
|
||||||
target["iscrowd"] = iscrowd
|
|
||||||
|
|
||||||
|
# Create the target dictionary
|
||||||
|
target = {
|
||||||
|
"boxes": boxes,
|
||||||
|
"labels": labels,
|
||||||
|
"masks": masks,
|
||||||
|
"image_id": torch.tensor([idx]),
|
||||||
|
"area": area,
|
||||||
|
"iscrowd": iscrowd,
|
||||||
|
}
|
||||||
|
else:
|
||||||
|
# Handle case with no valid objects (rare but possible)
|
||||||
|
target = {
|
||||||
|
"boxes": torch.zeros((0, 4), dtype=torch.float32),
|
||||||
|
"labels": torch.zeros((0,), dtype=torch.int64),
|
||||||
|
"masks": torch.zeros(
|
||||||
|
(0, mask.shape[0], mask.shape[1]), dtype=torch.uint8
|
||||||
|
),
|
||||||
|
"image_id": torch.tensor([idx]),
|
||||||
|
"area": torch.zeros((0,), dtype=torch.float32),
|
||||||
|
"iscrowd": torch.zeros((0,), dtype=torch.uint8),
|
||||||
|
}
|
||||||
|
|
||||||
|
# Apply transforms if provided
|
||||||
if self.transforms is not None:
|
if self.transforms is not None:
|
||||||
# Apply transforms to both image and target
|
|
||||||
# Note: torchvision v2 transforms handle target dicts automatically
|
|
||||||
img, target = self.transforms(img, target)
|
img, target = self.transforms(img, target)
|
||||||
|
|
||||||
return img, target
|
return img, target
|
||||||
@@ -117,15 +128,18 @@ def get_transform(train):
|
|||||||
# Convert to PyTorch tensor and normalize
|
# Convert to PyTorch tensor and normalize
|
||||||
transforms.append(T.ToImage())
|
transforms.append(T.ToImage())
|
||||||
|
|
||||||
# Add resize transform to reduce memory usage (max size of 800px)
|
# Resize images to control memory usage
|
||||||
transforms.append(T.Resize(800))
|
# Use a smaller size for training (more memory-intensive due to gradients)
|
||||||
|
if train:
|
||||||
|
transforms.append(T.Resize(700))
|
||||||
|
else:
|
||||||
|
transforms.append(T.Resize(800)) # Can use larger size for eval
|
||||||
|
|
||||||
transforms.append(T.ToDtype(torch.float32, scale=True))
|
transforms.append(T.ToDtype(torch.float32, scale=True))
|
||||||
|
|
||||||
# Data augmentation for training
|
# Data augmentation for training
|
||||||
if train:
|
if train:
|
||||||
transforms.append(T.RandomHorizontalFlip(0.5))
|
transforms.append(T.RandomHorizontalFlip(0.5))
|
||||||
# Could add more augmentations here if desired
|
|
||||||
|
|
||||||
return T.Compose(transforms)
|
return T.Compose(transforms)
|
||||||
|
|
||||||
|
|||||||
@@ -28,9 +28,13 @@ def evaluate(model, data_loader, device):
|
|||||||
images = list(image.to(device) for image in images)
|
images = list(image.to(device) for image in images)
|
||||||
targets = [{k: v.to(device) for k, v in t.items()} for t in targets]
|
targets = [{k: v.to(device) for k, v in t.items()} for t in targets]
|
||||||
|
|
||||||
# In eval mode with targets, Mask R-CNN should still return losses
|
# To handle the different behavior of Mask R-CNN in eval mode,
|
||||||
# If it returned predictions, logic here would change to process predictions
|
# we explicitly reset the model to training mode to compute losses,
|
||||||
|
# then switch back to eval mode for the rest of the evaluation
|
||||||
|
model.train()
|
||||||
loss_dict = model(images, targets)
|
loss_dict = model(images, targets)
|
||||||
|
model.eval()
|
||||||
|
|
||||||
losses = sum(loss for loss in loss_dict.values())
|
losses = sum(loss for loss in loss_dict.values())
|
||||||
loss_value = losses.item()
|
loss_value = losses.item()
|
||||||
total_loss += loss_value
|
total_loss += loss_value
|
||||||
|
|||||||
195
uv.lock
generated
195
uv.lock
generated
@@ -24,6 +24,56 @@ wheels = [
|
|||||||
{ url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335 },
|
{ url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335 },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "contourpy"
|
||||||
|
version = "1.3.1"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "numpy" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/25/c2/fc7193cc5383637ff390a712e88e4ded0452c9fbcf84abe3de5ea3df1866/contourpy-1.3.1.tar.gz", hash = "sha256:dfd97abd83335045a913e3bcc4a09c0ceadbe66580cf573fe961f4a825efa699", size = 13465753 }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/37/6b/175f60227d3e7f5f1549fcb374592be311293132207e451c3d7c654c25fb/contourpy-1.3.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:0ffa84be8e0bd33410b17189f7164c3589c229ce5db85798076a3fa136d0e509", size = 271494 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/6b/6a/7833cfae2c1e63d1d8875a50fd23371394f540ce809d7383550681a1fa64/contourpy-1.3.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:805617228ba7e2cbbfb6c503858e626ab528ac2a32a04a2fe88ffaf6b02c32bc", size = 255444 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/7f/b3/7859efce66eaca5c14ba7619791b084ed02d868d76b928ff56890d2d059d/contourpy-1.3.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ade08d343436a94e633db932e7e8407fe7de8083967962b46bdfc1b0ced39454", size = 307628 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/48/b2/011415f5e3f0a50b1e285a0bf78eb5d92a4df000553570f0851b6e309076/contourpy-1.3.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:47734d7073fb4590b4a40122b35917cd77be5722d80683b249dac1de266aac80", size = 347271 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/84/7d/ef19b1db0f45b151ac78c65127235239a8cf21a59d1ce8507ce03e89a30b/contourpy-1.3.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2ba94a401342fc0f8b948e57d977557fbf4d515f03c67682dd5c6191cb2d16ec", size = 318906 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/ba/99/6794142b90b853a9155316c8f470d2e4821fe6f086b03e372aca848227dd/contourpy-1.3.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:efa874e87e4a647fd2e4f514d5e91c7d493697127beb95e77d2f7561f6905bd9", size = 323622 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/3c/0f/37d2c84a900cd8eb54e105f4fa9aebd275e14e266736778bb5dccbf3bbbb/contourpy-1.3.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1bf98051f1045b15c87868dbaea84f92408337d4f81d0e449ee41920ea121d3b", size = 1266699 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/3a/8a/deb5e11dc7d9cc8f0f9c8b29d4f062203f3af230ba83c30a6b161a6effc9/contourpy-1.3.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:61332c87493b00091423e747ea78200659dc09bdf7fd69edd5e98cef5d3e9a8d", size = 1326395 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/1a/35/7e267ae7c13aaf12322ccc493531f1e7f2eb8fba2927b9d7a05ff615df7a/contourpy-1.3.1-cp312-cp312-win32.whl", hash = "sha256:e914a8cb05ce5c809dd0fe350cfbb4e881bde5e2a38dc04e3afe1b3e58bd158e", size = 175354 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/a1/35/c2de8823211d07e8a79ab018ef03960716c5dff6f4d5bff5af87fd682992/contourpy-1.3.1-cp312-cp312-win_amd64.whl", hash = "sha256:08d9d449a61cf53033612cb368f3a1b26cd7835d9b8cd326647efe43bca7568d", size = 220971 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/9a/e7/de62050dce687c5e96f946a93546910bc67e483fe05324439e329ff36105/contourpy-1.3.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:a761d9ccfc5e2ecd1bf05534eda382aa14c3e4f9205ba5b1684ecfe400716ef2", size = 271548 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/78/4d/c2a09ae014ae984c6bdd29c11e74d3121b25eaa117eca0bb76340efd7e1c/contourpy-1.3.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:523a8ee12edfa36f6d2a49407f705a6ef4c5098de4f498619787e272de93f2d5", size = 255576 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/ab/8a/915380ee96a5638bda80cd061ccb8e666bfdccea38d5741cb69e6dbd61fc/contourpy-1.3.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ece6df05e2c41bd46776fbc712e0996f7c94e0d0543af1656956d150c4ca7c81", size = 306635 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/29/5c/c83ce09375428298acd4e6582aeb68b1e0d1447f877fa993d9bf6cd3b0a0/contourpy-1.3.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:573abb30e0e05bf31ed067d2f82500ecfdaec15627a59d63ea2d95714790f5c2", size = 345925 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/29/63/5b52f4a15e80c66c8078a641a3bfacd6e07106835682454647aca1afc852/contourpy-1.3.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a9fa36448e6a3a1a9a2ba23c02012c43ed88905ec80163f2ffe2421c7192a5d7", size = 318000 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/9a/e2/30ca086c692691129849198659bf0556d72a757fe2769eb9620a27169296/contourpy-1.3.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ea9924d28fc5586bf0b42d15f590b10c224117e74409dd7a0be3b62b74a501c", size = 322689 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/6b/77/f37812ef700f1f185d348394debf33f22d531e714cf6a35d13d68a7003c7/contourpy-1.3.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5b75aa69cb4d6f137b36f7eb2ace9280cfb60c55dc5f61c731fdf6f037f958a3", size = 1268413 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/3f/6d/ce84e79cdd128542ebeb268f84abb4b093af78e7f8ec504676673d2675bc/contourpy-1.3.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:041b640d4ec01922083645a94bb3b2e777e6b626788f4095cf21abbe266413c1", size = 1326530 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/72/22/8282f4eae20c73c89bee7a82a19c4e27af9b57bb602ecaa00713d5bdb54d/contourpy-1.3.1-cp313-cp313-win32.whl", hash = "sha256:36987a15e8ace5f58d4d5da9dca82d498c2bbb28dff6e5d04fbfcc35a9cb3a82", size = 175315 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/e3/d5/28bca491f65312b438fbf076589dcde7f6f966b196d900777f5811b9c4e2/contourpy-1.3.1-cp313-cp313-win_amd64.whl", hash = "sha256:a7895f46d47671fa7ceec40f31fae721da51ad34bdca0bee83e38870b1f47ffd", size = 220987 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/2f/24/a4b285d6adaaf9746e4700932f579f1a7b6f9681109f694cfa233ae75c4e/contourpy-1.3.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:9ddeb796389dadcd884c7eb07bd14ef12408aaae358f0e2ae24114d797eede30", size = 285001 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/48/1d/fb49a401b5ca4f06ccf467cd6c4f1fd65767e63c21322b29b04ec40b40b9/contourpy-1.3.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:19c1555a6801c2f084c7ddc1c6e11f02eb6a6016ca1318dd5452ba3f613a1751", size = 268553 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/79/1e/4aef9470d13fd029087388fae750dccb49a50c012a6c8d1d634295caa644/contourpy-1.3.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:841ad858cff65c2c04bf93875e384ccb82b654574a6d7f30453a04f04af71342", size = 310386 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/b0/34/910dc706ed70153b60392b5305c708c9810d425bde12499c9184a1100888/contourpy-1.3.1-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4318af1c925fb9a4fb190559ef3eec206845f63e80fb603d47f2d6d67683901c", size = 349806 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/31/3c/faee6a40d66d7f2a87f7102236bf4780c57990dd7f98e5ff29881b1b1344/contourpy-1.3.1-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:14c102b0eab282427b662cb590f2e9340a9d91a1c297f48729431f2dcd16e14f", size = 321108 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/17/69/390dc9b20dd4bb20585651d7316cc3054b7d4a7b4f8b710b2b698e08968d/contourpy-1.3.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:05e806338bfeaa006acbdeba0ad681a10be63b26e1b17317bfac3c5d98f36cda", size = 327291 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/ef/74/7030b67c4e941fe1e5424a3d988080e83568030ce0355f7c9fc556455b01/contourpy-1.3.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:4d76d5993a34ef3df5181ba3c92fabb93f1eaa5729504fb03423fcd9f3177242", size = 1263752 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/f0/ed/92d86f183a8615f13f6b9cbfc5d4298a509d6ce433432e21da838b4b63f4/contourpy-1.3.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:89785bb2a1980c1bd87f0cb1517a71cde374776a5f150936b82580ae6ead44a1", size = 1318403 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/b3/0e/c8e4950c77dcfc897c71d61e56690a0a9df39543d2164040301b5df8e67b/contourpy-1.3.1-cp313-cp313t-win32.whl", hash = "sha256:8eb96e79b9f3dcadbad2a3891672f81cdcab7f95b27f28f1c67d75f045b6b4f1", size = 185117 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/c1/31/1ae946f11dfbd229222e6d6ad8e7bd1891d3d48bde5fbf7a0beb9491f8e3/contourpy-1.3.1-cp313-cp313t-win_amd64.whl", hash = "sha256:287ccc248c9e0d0566934e7d606201abd74761b5703d804ff3df8935f523d546", size = 236668 },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cycler"
|
||||||
|
version = "0.12.1"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/a9/95/a3dbbb5028f35eafb79008e7522a75244477d2838f38cbb722248dabc2a8/cycler-0.12.1.tar.gz", hash = "sha256:88bb128f02ba341da8ef447245a9e138fae777f6a23943da4540077d3601eb1c", size = 7615 }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl", hash = "sha256:85cef7cff222d8644161529808465972e51340599459b8ac3ccbac5a854e0d30", size = 8321 },
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "distlib"
|
name = "distlib"
|
||||||
version = "0.3.9"
|
version = "0.3.9"
|
||||||
@@ -42,6 +92,31 @@ wheels = [
|
|||||||
{ url = "https://files.pythonhosted.org/packages/4d/36/2a115987e2d8c300a974597416d9de88f2444426de9571f4b59b2cca3acc/filelock-3.18.0-py3-none-any.whl", hash = "sha256:c401f4f8377c4464e6db25fff06205fd89bdd83b65eb0488ed1b160f780e21de", size = 16215 },
|
{ url = "https://files.pythonhosted.org/packages/4d/36/2a115987e2d8c300a974597416d9de88f2444426de9571f4b59b2cca3acc/filelock-3.18.0-py3-none-any.whl", hash = "sha256:c401f4f8377c4464e6db25fff06205fd89bdd83b65eb0488ed1b160f780e21de", size = 16215 },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "fonttools"
|
||||||
|
version = "4.57.0"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/03/2d/a9a0b6e3a0cf6bd502e64fc16d894269011930cabfc89aee20d1635b1441/fonttools-4.57.0.tar.gz", hash = "sha256:727ece10e065be2f9dd239d15dd5d60a66e17eac11aea47d447f9f03fdbc42de", size = 3492448 }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/cb/98/d4bc42d43392982eecaaca117d79845734d675219680cd43070bb001bc1f/fonttools-4.57.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:889e45e976c74abc7256d3064aa7c1295aa283c6bb19810b9f8b604dfe5c7f31", size = 2751824 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/1a/62/7168030eeca3742fecf45f31e63b5ef48969fa230a672216b805f1d61548/fonttools-4.57.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:0425c2e052a5f1516c94e5855dbda706ae5a768631e9fcc34e57d074d1b65b92", size = 2283072 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/5d/82/121a26d9646f0986ddb35fbbaf58ef791c25b59ecb63ffea2aab0099044f/fonttools-4.57.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:44c26a311be2ac130f40a96769264809d3b0cb297518669db437d1cc82974888", size = 4788020 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/5b/26/e0f2fb662e022d565bbe280a3cfe6dafdaabf58889ff86fdef2d31ff1dde/fonttools-4.57.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:84c41ba992df5b8d680b89fd84c6a1f2aca2b9f1ae8a67400c8930cd4ea115f6", size = 4859096 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/9e/44/9075e323347b1891cdece4b3f10a3b84a8f4c42a7684077429d9ce842056/fonttools-4.57.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ea1e9e43ca56b0c12440a7c689b1350066595bebcaa83baad05b8b2675129d98", size = 4964356 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/48/28/caa8df32743462fb966be6de6a79d7f30393859636d7732e82efa09fbbb4/fonttools-4.57.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:84fd56c78d431606332a0627c16e2a63d243d0d8b05521257d77c6529abe14d8", size = 5226546 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/f6/46/95ab0f0d2e33c5b1a4fc1c0efe5e286ba9359602c0a9907adb1faca44175/fonttools-4.57.0-cp312-cp312-win32.whl", hash = "sha256:f4376819c1c778d59e0a31db5dc6ede854e9edf28bbfa5b756604727f7f800ac", size = 2146776 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/06/5d/1be5424bb305880e1113631f49a55ea7c7da3a5fe02608ca7c16a03a21da/fonttools-4.57.0-cp312-cp312-win_amd64.whl", hash = "sha256:57e30241524879ea10cdf79c737037221f77cc126a8cdc8ff2c94d4a522504b9", size = 2193956 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/e9/2f/11439f3af51e4bb75ac9598c29f8601aa501902dcedf034bdc41f47dd799/fonttools-4.57.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:408ce299696012d503b714778d89aa476f032414ae57e57b42e4b92363e0b8ef", size = 2739175 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/25/52/677b55a4c0972dc3820c8dba20a29c358197a78229daa2ea219fdb19e5d5/fonttools-4.57.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:bbceffc80aa02d9e8b99f2a7491ed8c4a783b2fc4020119dc405ca14fb5c758c", size = 2276583 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/64/79/184555f8fa77b827b9460a4acdbbc0b5952bb6915332b84c615c3a236826/fonttools-4.57.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f022601f3ee9e1f6658ed6d184ce27fa5216cee5b82d279e0f0bde5deebece72", size = 4766437 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/f8/ad/c25116352f456c0d1287545a7aa24e98987b6d99c5b0456c4bd14321f20f/fonttools-4.57.0-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4dea5893b58d4637ffa925536462ba626f8a1b9ffbe2f5c272cdf2c6ebadb817", size = 4838431 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/53/ae/398b2a833897297797a44f519c9af911c2136eb7aa27d3f1352c6d1129fa/fonttools-4.57.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:dff02c5c8423a657c550b48231d0a48d7e2b2e131088e55983cfe74ccc2c7cc9", size = 4951011 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/b7/5d/7cb31c4bc9ffb9a2bbe8b08f8f53bad94aeb158efad75da645b40b62cb73/fonttools-4.57.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:767604f244dc17c68d3e2dbf98e038d11a18abc078f2d0f84b6c24571d9c0b13", size = 5205679 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/4c/e4/6934513ec2c4d3d69ca1bc3bd34d5c69dafcbf68c15388dd3bb062daf345/fonttools-4.57.0-cp313-cp313-win32.whl", hash = "sha256:8e2e12d0d862f43d51e5afb8b9751c77e6bec7d2dc00aad80641364e9df5b199", size = 2144833 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/c4/0d/2177b7fdd23d017bcfb702fd41e47d4573766b9114da2fddbac20dcc4957/fonttools-4.57.0-cp313-cp313-win_amd64.whl", hash = "sha256:f1d6bc9c23356908db712d282acb3eebd4ae5ec6d8b696aa40342b1d84f8e9e3", size = 2190799 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/90/27/45f8957c3132917f91aaa56b700bcfc2396be1253f685bd5c68529b6f610/fonttools-4.57.0-py3-none-any.whl", hash = "sha256:3122c604a675513c68bd24c6a8f9091f1c2376d18e8f5fe5a101746c81b3e98f", size = 1093605 },
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "fsspec"
|
name = "fsspec"
|
||||||
version = "2025.3.2"
|
version = "2025.3.2"
|
||||||
@@ -81,6 +156,57 @@ wheels = [
|
|||||||
{ url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899 },
|
{ url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899 },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "kiwisolver"
|
||||||
|
version = "1.4.8"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/82/59/7c91426a8ac292e1cdd53a63b6d9439abd573c875c3f92c146767dd33faf/kiwisolver-1.4.8.tar.gz", hash = "sha256:23d5f023bdc8c7e54eb65f03ca5d5bb25b601eac4d7f1a042888a1f45237987e", size = 97538 }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/fc/aa/cea685c4ab647f349c3bc92d2daf7ae34c8e8cf405a6dcd3a497f58a2ac3/kiwisolver-1.4.8-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:d6af5e8815fd02997cb6ad9bbed0ee1e60014438ee1a5c2444c96f87b8843502", size = 124152 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/c5/0b/8db6d2e2452d60d5ebc4ce4b204feeb16176a851fd42462f66ade6808084/kiwisolver-1.4.8-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:bade438f86e21d91e0cf5dd7c0ed00cda0f77c8c1616bd83f9fc157fa6760d31", size = 66555 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/60/26/d6a0db6785dd35d3ba5bf2b2df0aedc5af089962c6eb2cbf67a15b81369e/kiwisolver-1.4.8-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b83dc6769ddbc57613280118fb4ce3cd08899cc3369f7d0e0fab518a7cf37fdb", size = 65067 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/c9/ed/1d97f7e3561e09757a196231edccc1bcf59d55ddccefa2afc9c615abd8e0/kiwisolver-1.4.8-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:111793b232842991be367ed828076b03d96202c19221b5ebab421ce8bcad016f", size = 1378443 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/29/61/39d30b99954e6b46f760e6289c12fede2ab96a254c443639052d1b573fbc/kiwisolver-1.4.8-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:257af1622860e51b1a9d0ce387bf5c2c4f36a90594cb9514f55b074bcc787cfc", size = 1472728 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/0c/3e/804163b932f7603ef256e4a715e5843a9600802bb23a68b4e08c8c0ff61d/kiwisolver-1.4.8-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:69b5637c3f316cab1ec1c9a12b8c5f4750a4c4b71af9157645bf32830e39c03a", size = 1478388 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/8a/9e/60eaa75169a154700be74f875a4d9961b11ba048bef315fbe89cb6999056/kiwisolver-1.4.8-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:782bb86f245ec18009890e7cb8d13a5ef54dcf2ebe18ed65f795e635a96a1c6a", size = 1413849 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/bc/b3/9458adb9472e61a998c8c4d95cfdfec91c73c53a375b30b1428310f923e4/kiwisolver-1.4.8-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cc978a80a0db3a66d25767b03688f1147a69e6237175c0f4ffffaaedf744055a", size = 1475533 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/e4/7a/0a42d9571e35798de80aef4bb43a9b672aa7f8e58643d7bd1950398ffb0a/kiwisolver-1.4.8-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:36dbbfd34838500a31f52c9786990d00150860e46cd5041386f217101350f0d3", size = 2268898 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/d9/07/1255dc8d80271400126ed8db35a1795b1a2c098ac3a72645075d06fe5c5d/kiwisolver-1.4.8-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:eaa973f1e05131de5ff3569bbba7f5fd07ea0595d3870ed4a526d486fe57fa1b", size = 2425605 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/84/df/5a3b4cf13780ef6f6942df67b138b03b7e79e9f1f08f57c49957d5867f6e/kiwisolver-1.4.8-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:a66f60f8d0c87ab7f59b6fb80e642ebb29fec354a4dfad687ca4092ae69d04f4", size = 2375801 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/8f/10/2348d068e8b0f635c8c86892788dac7a6b5c0cb12356620ab575775aad89/kiwisolver-1.4.8-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:858416b7fb777a53f0c59ca08190ce24e9abbd3cffa18886a5781b8e3e26f65d", size = 2520077 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/32/d8/014b89fee5d4dce157d814303b0fce4d31385a2af4c41fed194b173b81ac/kiwisolver-1.4.8-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:085940635c62697391baafaaeabdf3dd7a6c3643577dde337f4d66eba021b2b8", size = 2338410 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/bd/72/dfff0cc97f2a0776e1c9eb5bef1ddfd45f46246c6533b0191887a427bca5/kiwisolver-1.4.8-cp312-cp312-win_amd64.whl", hash = "sha256:01c3d31902c7db5fb6182832713d3b4122ad9317c2c5877d0539227d96bb2e50", size = 71853 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/dc/85/220d13d914485c0948a00f0b9eb419efaf6da81b7d72e88ce2391f7aed8d/kiwisolver-1.4.8-cp312-cp312-win_arm64.whl", hash = "sha256:a3c44cb68861de93f0c4a8175fbaa691f0aa22550c331fefef02b618a9dcb476", size = 65424 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/79/b3/e62464a652f4f8cd9006e13d07abad844a47df1e6537f73ddfbf1bc997ec/kiwisolver-1.4.8-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:1c8ceb754339793c24aee1c9fb2485b5b1f5bb1c2c214ff13368431e51fc9a09", size = 124156 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/8d/2d/f13d06998b546a2ad4f48607a146e045bbe48030774de29f90bdc573df15/kiwisolver-1.4.8-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:54a62808ac74b5e55a04a408cda6156f986cefbcf0ada13572696b507cc92fa1", size = 66555 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/59/e3/b8bd14b0a54998a9fd1e8da591c60998dc003618cb19a3f94cb233ec1511/kiwisolver-1.4.8-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:68269e60ee4929893aad82666821aaacbd455284124817af45c11e50a4b42e3c", size = 65071 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/f0/1c/6c86f6d85ffe4d0ce04228d976f00674f1df5dc893bf2dd4f1928748f187/kiwisolver-1.4.8-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:34d142fba9c464bc3bbfeff15c96eab0e7310343d6aefb62a79d51421fcc5f1b", size = 1378053 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/4e/b9/1c6e9f6dcb103ac5cf87cb695845f5fa71379021500153566d8a8a9fc291/kiwisolver-1.4.8-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3ddc373e0eef45b59197de815b1b28ef89ae3955e7722cc9710fb91cd77b7f47", size = 1472278 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/ee/81/aca1eb176de671f8bda479b11acdc42c132b61a2ac861c883907dde6debb/kiwisolver-1.4.8-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:77e6f57a20b9bd4e1e2cedda4d0b986ebd0216236f0106e55c28aea3d3d69b16", size = 1478139 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/49/f4/e081522473671c97b2687d380e9e4c26f748a86363ce5af48b4a28e48d06/kiwisolver-1.4.8-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:08e77738ed7538f036cd1170cbed942ef749137b1311fa2bbe2a7fda2f6bf3cc", size = 1413517 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/8f/e9/6a7d025d8da8c4931522922cd706105aa32b3291d1add8c5427cdcd66e63/kiwisolver-1.4.8-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a5ce1e481a74b44dd5e92ff03ea0cb371ae7a0268318e202be06c8f04f4f1246", size = 1474952 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/82/13/13fa685ae167bee5d94b415991c4fc7bb0a1b6ebea6e753a87044b209678/kiwisolver-1.4.8-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:fc2ace710ba7c1dfd1a3b42530b62b9ceed115f19a1656adefce7b1782a37794", size = 2269132 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/ef/92/bb7c9395489b99a6cb41d502d3686bac692586db2045adc19e45ee64ed23/kiwisolver-1.4.8-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:3452046c37c7692bd52b0e752b87954ef86ee2224e624ef7ce6cb21e8c41cc1b", size = 2425997 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/ed/12/87f0e9271e2b63d35d0d8524954145837dd1a6c15b62a2d8c1ebe0f182b4/kiwisolver-1.4.8-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:7e9a60b50fe8b2ec6f448fe8d81b07e40141bfced7f896309df271a0b92f80f3", size = 2376060 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/02/6e/c8af39288edbce8bf0fa35dee427b082758a4b71e9c91ef18fa667782138/kiwisolver-1.4.8-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:918139571133f366e8362fa4a297aeba86c7816b7ecf0bc79168080e2bd79957", size = 2520471 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/13/78/df381bc7b26e535c91469f77f16adcd073beb3e2dd25042efd064af82323/kiwisolver-1.4.8-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e063ef9f89885a1d68dd8b2e18f5ead48653176d10a0e324e3b0030e3a69adeb", size = 2338793 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/d0/dc/c1abe38c37c071d0fc71c9a474fd0b9ede05d42f5a458d584619cfd2371a/kiwisolver-1.4.8-cp313-cp313-win_amd64.whl", hash = "sha256:a17b7c4f5b2c51bb68ed379defd608a03954a1845dfed7cc0117f1cc8a9b7fd2", size = 71855 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/a0/b6/21529d595b126ac298fdd90b705d87d4c5693de60023e0efcb4f387ed99e/kiwisolver-1.4.8-cp313-cp313-win_arm64.whl", hash = "sha256:3cd3bc628b25f74aedc6d374d5babf0166a92ff1317f46267f12d2ed54bc1d30", size = 65430 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/34/bd/b89380b7298e3af9b39f49334e3e2a4af0e04819789f04b43d560516c0c8/kiwisolver-1.4.8-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:370fd2df41660ed4e26b8c9d6bbcad668fbe2560462cba151a721d49e5b6628c", size = 126294 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/83/41/5857dc72e5e4148eaac5aa76e0703e594e4465f8ab7ec0fc60e3a9bb8fea/kiwisolver-1.4.8-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:84a2f830d42707de1d191b9490ac186bf7997a9495d4e9072210a1296345f7dc", size = 67736 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/e1/d1/be059b8db56ac270489fb0b3297fd1e53d195ba76e9bbb30e5401fa6b759/kiwisolver-1.4.8-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:7a3ad337add5148cf51ce0b55642dc551c0b9d6248458a757f98796ca7348712", size = 66194 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/e1/83/4b73975f149819eb7dcf9299ed467eba068ecb16439a98990dcb12e63fdd/kiwisolver-1.4.8-cp313-cp313t-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7506488470f41169b86d8c9aeff587293f530a23a23a49d6bc64dab66bedc71e", size = 1465942 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/c7/2c/30a5cdde5102958e602c07466bce058b9d7cb48734aa7a4327261ac8e002/kiwisolver-1.4.8-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2f0121b07b356a22fb0414cec4666bbe36fd6d0d759db3d37228f496ed67c880", size = 1595341 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/ff/9b/1e71db1c000385aa069704f5990574b8244cce854ecd83119c19e83c9586/kiwisolver-1.4.8-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d6d6bd87df62c27d4185de7c511c6248040afae67028a8a22012b010bc7ad062", size = 1598455 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/85/92/c8fec52ddf06231b31cbb779af77e99b8253cd96bd135250b9498144c78b/kiwisolver-1.4.8-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:291331973c64bb9cce50bbe871fb2e675c4331dab4f31abe89f175ad7679a4d7", size = 1522138 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/0b/51/9eb7e2cd07a15d8bdd976f6190c0164f92ce1904e5c0c79198c4972926b7/kiwisolver-1.4.8-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:893f5525bb92d3d735878ec00f781b2de998333659507d29ea4466208df37bed", size = 1582857 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/0f/95/c5a00387a5405e68ba32cc64af65ce881a39b98d73cc394b24143bebc5b8/kiwisolver-1.4.8-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:b47a465040146981dc9db8647981b8cb96366fbc8d452b031e4f8fdffec3f26d", size = 2293129 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/44/83/eeb7af7d706b8347548313fa3a3a15931f404533cc54fe01f39e830dd231/kiwisolver-1.4.8-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:99cea8b9dd34ff80c521aef46a1dddb0dcc0283cf18bde6d756f1e6f31772165", size = 2421538 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/05/f9/27e94c1b3eb29e6933b6986ffc5fa1177d2cd1f0c8efc5f02c91c9ac61de/kiwisolver-1.4.8-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:151dffc4865e5fe6dafce5480fab84f950d14566c480c08a53c663a0020504b6", size = 2390661 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/d9/d4/3c9735faa36ac591a4afcc2980d2691000506050b7a7e80bcfe44048daa7/kiwisolver-1.4.8-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:577facaa411c10421314598b50413aa1ebcf5126f704f1e5d72d7e4e9f020d90", size = 2546710 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/4c/fa/be89a49c640930180657482a74970cdcf6f7072c8d2471e1babe17a222dc/kiwisolver-1.4.8-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:be4816dc51c8a471749d664161b434912eee82f2ea66bd7628bd14583a833e85", size = 2349213 },
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "markupsafe"
|
name = "markupsafe"
|
||||||
version = "3.0.2"
|
version = "3.0.2"
|
||||||
@@ -119,6 +245,43 @@ wheels = [
|
|||||||
{ url = "https://files.pythonhosted.org/packages/4f/65/6079a46068dfceaeabb5dcad6d674f5f5c61a6fa5673746f42a9f4c233b3/MarkupSafe-3.0.2-cp313-cp313t-win_amd64.whl", hash = "sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f", size = 15739 },
|
{ url = "https://files.pythonhosted.org/packages/4f/65/6079a46068dfceaeabb5dcad6d674f5f5c61a6fa5673746f42a9f4c233b3/MarkupSafe-3.0.2-cp313-cp313t-win_amd64.whl", hash = "sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f", size = 15739 },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "matplotlib"
|
||||||
|
version = "3.10.1"
|
||||||
|
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/2f/08/b89867ecea2e305f408fbb417139a8dd941ecf7b23a2e02157c36da546f0/matplotlib-3.10.1.tar.gz", hash = "sha256:e8d2d0e3881b129268585bf4765ad3ee73a4591d77b9a18c214ac7e3a79fb2ba", size = 36743335 }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/7c/1d/5e0dc3b59c034e43de16f94deb68f4ad8a96b3ea00f4b37c160b7474928e/matplotlib-3.10.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:66e907a06e68cb6cfd652c193311d61a12b54f56809cafbed9736ce5ad92f107", size = 8175488 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/7a/81/dae7e14042e74da658c3336ab9799128e09a1ee03964f2d89630b5d12106/matplotlib-3.10.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:e9b4bb156abb8fa5e5b2b460196f7db7264fc6d62678c03457979e7d5254b7be", size = 8046264 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/21/c4/22516775dcde10fc9c9571d155f90710761b028fc44f660508106c363c97/matplotlib-3.10.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1985ad3d97f51307a2cbfc801a930f120def19ba22864182dacef55277102ba6", size = 8452048 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/63/23/c0615001f67ce7c96b3051d856baedc0c818a2ed84570b9bf9bde200f85d/matplotlib-3.10.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c96f2c2f825d1257e437a1482c5a2cf4fee15db4261bd6fc0750f81ba2b4ba3d", size = 8597111 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/ca/c0/a07939a82aed77770514348f4568177d7dadab9787ebc618a616fe3d665e/matplotlib-3.10.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:35e87384ee9e488d8dd5a2dd7baf471178d38b90618d8ea147aced4ab59c9bea", size = 9402771 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/a6/b6/a9405484fb40746fdc6ae4502b16a9d6e53282ba5baaf9ebe2da579f68c4/matplotlib-3.10.1-cp312-cp312-win_amd64.whl", hash = "sha256:cfd414bce89cc78a7e1d25202e979b3f1af799e416010a20ab2b5ebb3a02425c", size = 8063742 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/60/73/6770ff5e5523d00f3bc584acb6031e29ee5c8adc2336b16cd1d003675fe0/matplotlib-3.10.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:c42eee41e1b60fd83ee3292ed83a97a5f2a8239b10c26715d8a6172226988d7b", size = 8176112 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/08/97/b0ca5da0ed54a3f6599c3ab568bdda65269bc27c21a2c97868c1625e4554/matplotlib-3.10.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:4f0647b17b667ae745c13721602b540f7aadb2a32c5b96e924cd4fea5dcb90f1", size = 8046931 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/df/9a/1acbdc3b165d4ce2dcd2b1a6d4ffb46a7220ceee960c922c3d50d8514067/matplotlib-3.10.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aa3854b5f9473564ef40a41bc922be978fab217776e9ae1545c9b3a5cf2092a3", size = 8453422 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/51/d0/2bc4368abf766203e548dc7ab57cf7e9c621f1a3c72b516cc7715347b179/matplotlib-3.10.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e496c01441be4c7d5f96d4e40f7fca06e20dcb40e44c8daa2e740e1757ad9e6", size = 8596819 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/ab/1b/8b350f8a1746c37ab69dda7d7528d1fc696efb06db6ade9727b7887be16d/matplotlib-3.10.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5d45d3f5245be5b469843450617dcad9af75ca50568acf59997bed9311131a0b", size = 9402782 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/89/06/f570373d24d93503988ba8d04f213a372fa1ce48381c5eb15da985728498/matplotlib-3.10.1-cp313-cp313-win_amd64.whl", hash = "sha256:8e8e25b1209161d20dfe93037c8a7f7ca796ec9aa326e6e4588d8c4a5dd1e473", size = 8063812 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/fc/e0/8c811a925b5a7ad75135f0e5af46408b78af88bbb02a1df775100ef9bfef/matplotlib-3.10.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:19b06241ad89c3ae9469e07d77efa87041eac65d78df4fcf9cac318028009b01", size = 8214021 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/4a/34/319ec2139f68ba26da9d00fce2ff9f27679fb799a6c8e7358539801fd629/matplotlib-3.10.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:01e63101ebb3014e6e9f80d9cf9ee361a8599ddca2c3e166c563628b39305dbb", size = 8090782 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/77/ea/9812124ab9a99df5b2eec1110e9b2edc0b8f77039abf4c56e0a376e84a29/matplotlib-3.10.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3f06bad951eea6422ac4e8bdebcf3a70c59ea0a03338c5d2b109f57b64eb3972", size = 8478901 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/c9/db/b05bf463689134789b06dea85828f8ebe506fa1e37593f723b65b86c9582/matplotlib-3.10.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a3dfb036f34873b46978f55e240cff7a239f6c4409eac62d8145bad3fc6ba5a3", size = 8613864 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/c2/04/41ccec4409f3023a7576df3b5c025f1a8c8b81fbfe922ecfd837ac36e081/matplotlib-3.10.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:dc6ab14a7ab3b4d813b88ba957fc05c79493a037f54e246162033591e770de6f", size = 9409487 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/ac/c2/0d5aae823bdcc42cc99327ecdd4d28585e15ccd5218c453b7bcd827f3421/matplotlib-3.10.1-cp313-cp313t-win_amd64.whl", hash = "sha256:bc411ebd5889a78dabbc457b3fa153203e22248bfa6eedc6797be5df0164dbf9", size = 8134832 },
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "mpmath"
|
name = "mpmath"
|
||||||
version = "1.3.0"
|
version = "1.3.0"
|
||||||
@@ -383,6 +546,15 @@ wheels = [
|
|||||||
{ url = "https://files.pythonhosted.org/packages/88/74/a88bf1b1efeae488a0c0b7bdf71429c313722d1fc0f377537fbe554e6180/pre_commit-4.2.0-py2.py3-none-any.whl", hash = "sha256:a009ca7205f1eb497d10b845e52c838a98b6cdd2102a6c8e4540e94ee75c58bd", size = 220707 },
|
{ url = "https://files.pythonhosted.org/packages/88/74/a88bf1b1efeae488a0c0b7bdf71429c313722d1fc0f377537fbe554e6180/pre_commit-4.2.0-py2.py3-none-any.whl", hash = "sha256:a009ca7205f1eb497d10b845e52c838a98b6cdd2102a6c8e4540e94ee75c58bd", size = 220707 },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[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 }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/05/e7/df2285f3d08fee213f2d041540fa4fc9ca6c2d44cf36d3a035bf2a8d2bcc/pyparsing-3.2.3-py3-none-any.whl", hash = "sha256:a749938e02d6fd0b59b356ca504a24982314bb090c383e3cf201c95ef7e2bfcf", size = 111120 },
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pytest"
|
name = "pytest"
|
||||||
version = "8.3.5"
|
version = "8.3.5"
|
||||||
@@ -398,6 +570,18 @@ wheels = [
|
|||||||
{ url = "https://files.pythonhosted.org/packages/30/3d/64ad57c803f1fa1e963a7946b6e0fea4a70df53c1a7fed304586539c2bac/pytest-8.3.5-py3-none-any.whl", hash = "sha256:c69214aa47deac29fad6c2a4f590b9c4a9fdb16a403176fe154b79c0b4d4d820", size = 343634 },
|
{ url = "https://files.pythonhosted.org/packages/30/3d/64ad57c803f1fa1e963a7946b6e0fea4a70df53c1a7fed304586539c2bac/pytest-8.3.5-py3-none-any.whl", hash = "sha256:c69214aa47deac29fad6c2a4f590b9c4a9fdb16a403176fe154b79c0b4d4d820", size = 343634 },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "python-dateutil"
|
||||||
|
version = "2.9.0.post0"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "six" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/66/c0/0c8b6ad9f17a802ee498c46e004a0eb49bc148f2fd230864601a86dcf6db/python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 342432 }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892 },
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pyyaml"
|
name = "pyyaml"
|
||||||
version = "6.0.2"
|
version = "6.0.2"
|
||||||
@@ -458,6 +642,15 @@ wheels = [
|
|||||||
{ url = "https://files.pythonhosted.org/packages/54/21/f43f0a1fa8b06b32812e0975981f4677d28e0f3271601dc88ac5a5b83220/setuptools-78.1.0-py3-none-any.whl", hash = "sha256:3e386e96793c8702ae83d17b853fb93d3e09ef82ec62722e61da5cd22376dcd8", size = 1256108 },
|
{ url = "https://files.pythonhosted.org/packages/54/21/f43f0a1fa8b06b32812e0975981f4677d28e0f3271601dc88ac5a5b83220/setuptools-78.1.0-py3-none-any.whl", hash = "sha256:3e386e96793c8702ae83d17b853fb93d3e09ef82ec62722e61da5cd22376dcd8", size = 1256108 },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "six"
|
||||||
|
version = "1.17.0"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81", size = 34031 }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050 },
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "sympy"
|
name = "sympy"
|
||||||
version = "1.13.1"
|
version = "1.13.1"
|
||||||
@@ -570,6 +763,7 @@ name = "torchvision-vibecoding-project"
|
|||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = { virtual = "." }
|
source = { virtual = "." }
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
{ name = "matplotlib" },
|
||||||
{ name = "numpy" },
|
{ name = "numpy" },
|
||||||
{ name = "pillow" },
|
{ name = "pillow" },
|
||||||
{ name = "pytest" },
|
{ name = "pytest" },
|
||||||
@@ -587,6 +781,7 @@ dev = [
|
|||||||
|
|
||||||
[package.metadata]
|
[package.metadata]
|
||||||
requires-dist = [
|
requires-dist = [
|
||||||
|
{ name = "matplotlib", specifier = ">=3.10.1" },
|
||||||
{ name = "numpy", specifier = ">=2.2.4" },
|
{ name = "numpy", specifier = ">=2.2.4" },
|
||||||
{ name = "pillow", specifier = ">=11.1.0" },
|
{ name = "pillow", specifier = ">=11.1.0" },
|
||||||
{ name = "pytest", specifier = ">=8.3.5" },
|
{ name = "pytest", specifier = ">=8.3.5" },
|
||||||
|
|||||||
Reference in New Issue
Block a user