Pausing for now, train loop should now work and adding some tests

This commit is contained in:
Craig
2025-04-12 12:01:13 +01:00
parent 2b38c04a57
commit be70c4e160
13 changed files with 967 additions and 58 deletions

25
configs/debug_config.py Normal file
View 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
}
)

View File

@@ -5,6 +5,7 @@ description = "Add your description here"
readme = "README.md"
requires-python = ">=3.12"
dependencies = [
"matplotlib>=3.10.1",
"numpy>=2.2.4",
"pillow>=11.1.0",
"pytest>=8.3.5",

152
scripts/test_model.py Executable file
View 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
View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 944 KiB

View File

@@ -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
View 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
View 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"

View 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)

View File

@@ -18,80 +18,91 @@ class PennFudanDataset(torch.utils.data.Dataset):
self.masks = sorted(list(os.listdir(os.path.join(root, "PedMasks"))))
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])
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")
# Note: Masks are not converted to RGB, contains index values
mask = Image.open(mask_path)
# Convert mask to numpy array
# Convert mask PIL image to numpy array
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)
# First id is the background, so remove it
obj_ids = obj_ids[1:]
obj_ids = obj_ids[1:] # Remove background (id=0)
# Split the color-encoded mask into a set of binary masks
binary_masks = mask == obj_ids[:, None, None]
# Split the mask into binary masks for each object instance
masks = mask == obj_ids[:, None, None]
# Get bounding box coordinates for each mask
# Get bounding box for each mask
num_objs = len(obj_ids)
boxes = []
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])
xmax = np.max(pos[1])
ymin = np.min(pos[0])
ymax = np.max(pos[0])
# Filter out potentially empty masks or masks with zero area
if xmax > xmin and ymax > ymin:
# Skip boxes with zero area
if xmax <= xmin or ymax <= ymin:
continue
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
if not boxes:
# Handle case with no valid boxes found - return dummy target? Or raise error?
# For now, let's create dummy tensors. This should be revisited.
print(
f"Warning: No valid boxes found for image {idx}. Returning dummy target."
)
boxes = torch.zeros((0, 4), dtype=torch.float32)
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:
# Convert everything to tensors
if boxes:
boxes = torch.as_tensor(boxes, dtype=torch.float32)
# There is only one class (pedestrian)
labels = torch.ones((num_objs,), dtype=torch.int64)
binary_masks = torch.as_tensor(binary_masks, dtype=torch.uint8)
image_id = torch.tensor([idx])
# Calculate area
labels = torch.ones(
(len(boxes),), dtype=torch.int64
) # All objects are pedestrians (class 1)
masks = torch.as_tensor(masks, dtype=torch.uint8)
# Calculate area of each box
area = (boxes[:, 3] - boxes[:, 1]) * (boxes[:, 2] - boxes[:, 0])
# Assume all instances are not crowd
iscrowd = torch.zeros((num_objs,), dtype=torch.uint8)
target = {}
target["boxes"] = boxes
target["labels"] = labels
target["masks"] = binary_masks
target["image_id"] = image_id
target["area"] = area
target["iscrowd"] = iscrowd
# All instances are not crowd
iscrowd = torch.zeros((len(boxes),), dtype=torch.uint8)
# 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:
# Apply transforms to both image and target
# Note: torchvision v2 transforms handle target dicts automatically
img, target = self.transforms(img, target)
return img, target
@@ -117,15 +128,18 @@ def get_transform(train):
# Convert to PyTorch tensor and normalize
transforms.append(T.ToImage())
# Add resize transform to reduce memory usage (max size of 800px)
transforms.append(T.Resize(800))
# Resize images to control memory usage
# 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))
# Data augmentation for training
if train:
transforms.append(T.RandomHorizontalFlip(0.5))
# Could add more augmentations here if desired
return T.Compose(transforms)

View File

@@ -28,9 +28,13 @@ def evaluate(model, data_loader, device):
images = list(image.to(device) for image in images)
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
# If it returned predictions, logic here would change to process predictions
# To handle the different behavior of Mask R-CNN in eval mode,
# 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)
model.eval()
losses = sum(loss for loss in loss_dict.values())
loss_value = losses.item()
total_loss += loss_value

195
uv.lock generated
View File

@@ -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 },
]
[[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]]
name = "distlib"
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 },
]
[[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]]
name = "fsspec"
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 },
]
[[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]]
name = "markupsafe"
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 },
]
[[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]]
name = "mpmath"
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 },
]
[[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]]
name = "pytest"
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 },
]
[[package]]
name = "python-dateutil"
version = "2.9.0.post0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "six" },
]
sdist = { url = "https://files.pythonhosted.org/packages/66/c0/0c8b6ad9f17a802ee498c46e004a0eb49bc148f2fd230864601a86dcf6db/python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 342432 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892 },
]
[[package]]
name = "pyyaml"
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 },
]
[[package]]
name = "six"
version = "1.17.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81", size = 34031 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050 },
]
[[package]]
name = "sympy"
version = "1.13.1"
@@ -570,6 +763,7 @@ name = "torchvision-vibecoding-project"
version = "0.1.0"
source = { virtual = "." }
dependencies = [
{ name = "matplotlib" },
{ name = "numpy" },
{ name = "pillow" },
{ name = "pytest" },
@@ -587,6 +781,7 @@ dev = [
[package.metadata]
requires-dist = [
{ name = "matplotlib", specifier = ">=3.10.1" },
{ name = "numpy", specifier = ">=2.2.4" },
{ name = "pillow", specifier = ">=11.1.0" },
{ name = "pytest", specifier = ">=8.3.5" },